For Zoho Services only:


I'm actually part of something bigger at Ascent Business Solutions recognized as the top Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions offer support for smaller technical fixes and projects for larger developments, such as migrating to a ZohoCRM.  A team rather than a one-man-band is always available to ensure seamless progress and address any concerns. You'll find our competitive support rates with flexible, no-expiration bundles at http://ascentbusiness.co.uk/zoho-support-2.  For larger projects, check our bespoke pricing structure and receive dedicated support from our hands-on project consultants and developers at http://ascentbusiness.co.uk/crm-solutions/zoho-crm-packages-prices.

The team I manage specializes in coding API integrations between Zoho and third-party finance/commerce suites such as Xero, Shopify, WooCommerce, and eBay; to name but a few.  Our passion lies in creating innovative solutions where others have fallen short as well as working with new businesses, new sectors, and new ideas.  Our success is measured by the growth and ROI we deliver for clients, such as transforming a garden shed hobby into a 250k monthly turnover operation or generating a +60% return in just three days after launch through online payments and a streamlined e-commerce solution, replacing a paper-based system.

If you're looking for a partner who can help you drive growth and success, we'd love to work with you.  You can reach out to us on 0121 392 8140 (UK) or info@ascentbusiness.co.uk.  You can also visit our website at http://ascentbusiness.co.uk.

Zoho CRM: APIv2 using PHP & cURL

What?
This is an article documenting how to access ZohoCRM with API v2 using PHP and cURL. The first few functions are to manage OAuth v2 and generate the refresh and access tokens. The second snippet of code below is using the functions to read data from Zoho CRM and to write data back to the system.

Why?
I've rewritten this code a few times and want to store the finalized version (following updates) making it as generic as I can in order to apply it to any client.

How?
Firstly, you will need to browse to https://accounts.zoho.eu/developerconsole and register your new app (or the one you will have completed once copying the below scripts).

Let's start with the first PHP file which I will call functions.php. Note that you will need to edit the global vars to be used by the functions located at the top of the script:
copyraw
<?php

/*
	------------------------------------------------------------------------------------------------
	Zoho Authorization via oAuth2.0 for REST API v2
	------------------------------------------------------------------------------------------------

	Zoho API v2:	https://accounts.zoho.eu/developerconsole

	Documentation:	https://www.zoho.com/crm/help/api/v2/

	Available Scopes
	    users		users.all
	    org			org.all
	    settings	settings.all, settings.territories, settings.custom_views, settings.related_lists,
	    			settings.modules, settings.tab_groups, settings.fields, settings.layouts,
	    			settings.macros, settings.custom_links, settings.custom_buttons, settings.roles,
	    			settings.profiles
	    modules		modules.all, modules.approvals, modules.leads, modules.accounts, modules.contacts,
	    			modules.deals, modules.campaigns, modules.tasks, modules.cases, modules.events,
	    			modules.calls, modules.solutions, modules.products, modules.vendors,
	    			modules.pricebooks, modules.quotes, modules.salesorders, modules.purchaseorders,
	    			modules.invoices, modules.custom, modules.dashboards, modules.notes,
	    			modules.activities, modules.search

	Possible Module Names
		leads, accounts, contacts, deals, campaigns, tasks, cases, events, calls, solutions, products,
		vendors, pricebooks, quotes, salesorders, purchaseorders, invoices, custom, notes, approvals,
		dashboards, search, activities

*/

	// Global vars for Zoho API
	$zoho_apis_com = "https://www.zohoapis.com";
	$zoho_apis_eu = "https://www.zohoapis.eu";
	$refresh_access_token_url = "https://accounts.zoho.eu/oauth/v2/token";

	// Endpoint: Sandbox  // disable after testing
	$zoho_sandbox = "https://sandbox.zohoapis.eu";
	$zoho_sandbox_domain = "https://crmsandbox.zoho.eu/crm";

	// Global vars to be used by below functions specific to this app
	$zoho_client_id = "1000.your_client_id";
	$zoho_client_secret = "your_client_secret";
	$zoho_redirect_uri = "your_redirect_uri";  // will be URL to start.php in this example
	$access_token_path = "path_to_your_access_token_not_on_www_but_accessible_by_script/access_token.dat";
	$refresh_token_path = "path_to_your_access_token_not_on_www_but_accessible_by_script/refresh_token.dat";

	// function to use Zoho API v2
	function abZohoApi( $post_url, $post_fields, $post_header=false, $post_type='GET' )
	{
		// setup cURL request
		$ch=curl_init();

		// do not return header information
		curl_setopt($ch, CURLOPT_HEADER, 0);

		// submit data in header if specified
		if(is_array($post_header)){
			curl_setopt($ch, CURLOPT_HTTPHEADER, $post_header);
		}

		// do not return status info
		curl_setopt($ch, CURLOPT_VERBOSE, 0);

		// return data
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

		// cancel ssl checks
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

		// if using GET, POST or PUT
		if($post_type=='POST'){
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
			curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
		} else if($post_type=='PUT'){
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
			curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
		} else if($post_type=='DELETE'){
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
		}else{
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
			if($post_fields){
				$post_url.='?'.http_build_query($post_fields);
			}
		}

		// specified endpoint
		curl_setopt($ch, CURLOPT_URL, $post_url);

		// execute cURL request
		$response=curl_exec($ch);

		// return errors if any
		if (curl_exec($ch) === false) {
			$output = curl_error($ch);
		} else {
			$output = $response;
		}

		// close cURL handle
		curl_close($ch);

		// output
		return $output;
	}

	// Generate the refresh token by manually entering the following in the browser
	// Browse to https://accounts.zoho.eu/oauth/v2/auth?scope=ZohoCRM.modules.ALL,ZohoCRM.users.READ,ZohoCRM.settings.fields.read&client_id=your_client_id&response_type=code&access_type=offline&redirect_uri=your_url_path_to_start_page/start.php&prompt=consent

	// function to generate refresh token from Zoho authorization code
	function generate_refresh_token(){

		// use vars declared at beginning of script
		global $zoho_client_id, $zoho_client_secret, $zoho_redirect_uri, $access_token_path, $refresh_token_path;

		// Generate Access Token and Refresh Token - Read url GET values
		$zoho_grant_token = $_GET['code'];
		$zoho_location = $_GET['location'];
		$zoho_accounts_server = $_GET['accounts-server'];

		// Generate Access Token and Refresh Token
		$url_auth=urldecode($zoho_accounts_server)."/oauth/v2/token";

		// Build fields to post
		$fields_token=array("code"=>$zoho_grant_token, "redirect_uri"=>$zoho_redirect_uri, "client_id"=>$zoho_client_id, "client_secret"=>$zoho_client_secret, "grant_type"=>"authorization_code", "prompt"=>"consent");

		// Generate Access Token and Refresh Token - post via cURL
		$response_json = abZohoApi($url_auth, $fields_token, false, 'POST');

		// Generate Access Token and Refresh Token - format output (convert JSON to Object)
		$refresh_token_arr = json_decode($response_json, true);

		// store in var
		$refresh_token = isset($refresh_token_arr['refresh_token']) ? $refresh_token_arr['refresh_token'] : 0;

		// encode value to base64
		$refresh_token_base64 = base64_encode($refresh_token);

		// store encoded value to file
		file_put_contents($refresh_token_path, $refresh_token_base64);

		// -- do access token while we're here
		// store in access token
		$access_token = isset($refresh_token_arr['access_token']) ? $refresh_token_arr['access_token'] : 0;

		// encode value to base64
		$access_token_base64 = base64_encode($access_token);

		// store encoded value to file
		file_put_contents($access_token_path, $access_token_base64);

		// return array of json objects
		return $refresh_token_arr;
	}


	// function to generate access token from refresh token
	// returns minutes remaining of valid token
	function generate_access_token(){

		// use vars declared at beginning of script
		global $zoho_client_id, $zoho_client_secret, $access_token_path, $refresh_token_path, $refresh_access_token_url;

		// get refresh token from file
		$refresh_token = base64_decode( file_get_contents( $refresh_token_path ) );

		// build fields to post
		$refresh_fields = array("refresh_token" => $refresh_token, "client_id" => $zoho_client_id, "client_secret" => $zoho_client_secret, "grant_type" => "refresh_token");

		// send to Zoho API
		$this_access_token_json = abZohoApi($refresh_access_token_url, $refresh_fields, false, 'POST');

		// convert JSON response to array
		$access_token_arr = json_decode($this_access_token_json, true);

		// store in var
		$returned_token = $access_token_arr['access_token'];

		// encode value to base64
		$access_token_base64 = base64_encode($returned_token);

		// store encoded value to file
		file_put_contents($access_token_path, $access_token_base64);
	}

	// function to decode and read access token from file
	function read_token($file){

		// get access token from file
		$token_base64 = file_get_contents($file);

		// decode value to token
		$token = base64_decode($token_base64);

		// output
		return $token;
	}

	// function to sort our returned data (multidimensional array)
	function array_sort_by_column(&$arr, $col, $dir = SORT_DESC) {
		$sort_col = array();
		foreach ($arr as $key=> $row) {
			$sort_col[$key] = strtolower($row[$col]); // strtolower to make it case insensitive
		}
		array_multisort($sort_col, $dir, $arr);
	}

	// function to get minutes left on a generated file
	// defaults to an hour expiry time
	// usage: get_time_remaining( 'access.dat', 3600)
	function get_time_remaining($file, $expiry_in_seconds=3600){

		// get file modified time
		$file_modified_time = filemtime($file);

		// add 1 hour
		$file_expiry_time = $file_modified_time + $expiry_in_seconds;

		// calculate seconds left
		$diff = $file_expiry_time - time();

		// round to minutes
		$minutes = floor($diff/60);

		// output
		return $minutes;
	}


	// function to check access token and regenerate if necessary
	function check_access_token(){

		global $access_token_path;

		// get time remaining on access token (1 hour max)
		$access_token_time_remaining = get_time_remaining($access_token_path);

		// if less than 5 minutes left, regenerate token
		if($access_token_time_remaining<=5){

			// Generate Access Token from Refresh Token
			generate_access_token();

			// update time remaining on access token (again)
			$access_token_time_remaining = get_time_remaining($access_token_path);
		}

		// return time remaining (in minutes)
		return $access_token_time_remaining;

	}


	// get data: returns PHP Array (for functional sorting: PHP & JS)
	// usage: Leads: get_records("Leads")
	// usage: Lead: get_records("Leads", 98304820934029840)
	// usage: User: get_records("users", 10825000000119017)  // note that users has to be lowercase
	function get_records($zoho_category, $zoho_id=0, $fields_data=array()){

		global $access_token_path, $zoho_apis_eu;

		// get access token
		$access_token = read_token($access_token_path);

		// endpoint
		$url_data=$zoho_apis_eu."/crm/v2/".$zoho_category;

		// if array (eg. Related Records), accept ID and related_list_apiname
		if(is_array($zoho_id)){
			$url_data.= $zoho_id[0]>0 ? '/'.$zoho_id[0].'/'.$zoho_id[1] : '';
		}else{
			// add id if exists
			$url_data.= $zoho_id!=0 && $zoho_id!="" ? '/'.$zoho_id : '';
		}

		// add access token to header
		$header_data = array("Authorization: Zoho-oauthtoken ".$access_token);

		// send to Zoho API
		$response_json = abZohoApi($url_data, $fields_data, $header_data);

		// convert response to PHP array (for sorting)
		$response_arr = json_decode($response_json, true);

		// output
		return $response_arr;
	}

	// get data: returns PHP Array (for functional sorting: PHP & JS)
	// usage: Leads: search_records("Leads", array('Last_Name:starts_with:G', 'Email:equals:This email address is being protected from spambots. You need JavaScript enabled to view it.'))
	function search_records($zoho_category, $criteria=array()){

		global $access_token_path, $zoho_apis_eu;

		// get access token
		$access_token = read_token($access_token_path);

		// endpoint
		$url_data=$zoho_apis_eu."/crm/v2/".$zoho_category."/search?criteria=";

		// join the criteria
		if(count($criteria)==1){
			$url_data.= '('.$criteria[0].')';
		}elseif(count($criteria)>1){
			$url_data.= '(('.implode($criteria, ') and (').'))';
		}

		// add access token to header
		$header_data=array("Authorization: Zoho-oauthtoken ".$access_token);

		// send to Zoho API
		$response_json = abZohoApi($url_data, false, $header_data);

		// convert response to PHP array (for sorting)
		$response_arr = json_decode($response_json, true);

		// output
		return $response_arr;
	}

	// function to retrieve current user record
	// accepts User ID as parameter
	// returns array( authorized{ok,fail}, name, email, profile, role )
	function authenticate_user($user_id){

		global $access_token_path;

		// get access token
		$token = read_token($access_token_path);

		// pass parameter requesting only active users who are also confirmed
		$user_fields = array("type"=>"ActiveConfirmedUsers");

		// authenticate user (id was stored in cookie from GET var when this app was initially loaded)
		$zoho_user_record = get_records("users", $user_id, $user_fields);

		// failed by default
		$user_authorized = 'fail';
		$zoho_user_name=$zoho_user_email=$zoho_user_profile=$zoho_user_role="";
		if(isset($zoho_user_record['users'])){

			// record is readable
			$zoho_user_isactive = $zoho_user_record['users'][0]['status'];

			// if status is active
			if($zoho_user_isactive=='active'){
				$user_authorized = 'ok';
				$zoho_user_name = $zoho_user_record['users'][0]['full_name'];

				// user email
				$zoho_user_email = $zoho_user_record['users'][0]['email'];

				// user profile
				$zoho_user_profile = $zoho_user_record['users'][0]['profile']['name'];

				// user role
				$zoho_user_role = $zoho_user_record['users'][0]['role']['name'];
			}
		}

		// return vars
		return array("authorized"=>$user_authorized, "name"=>$zoho_user_name, "email"=>$zoho_user_email, "profile"=>$zoho_user_profile, "role"=>$zoho_user_role);
	}


	// function to ensure data was transferred
	// accepts array (JSON Response)
	// returns boolean
	function check_data_is_valid($data){
		$is_valid = false;
		if(isset($data['data'])){
			$is_valid = true;
		}
		return $is_valid;
	}
  1.  <?php 
  2.   
  3.  /* 
  4.      ------------------------------------------------------------------------------------------------ 
  5.      Zoho Authorization via oAuth2.0 for REST API v2 
  6.      ------------------------------------------------------------------------------------------------ 
  7.   
  8.      Zoho API v2:    https://accounts.zoho.eu/developerconsole 
  9.   
  10.      Documentation:    https://www.zoho.com/crm/help/api/v2/ 
  11.   
  12.      Available Scopes 
  13.          users        users.all 
  14.          org            org.all 
  15.          settings    settings.all, settings.territories, settings.custom_views, settings.related_lists, 
  16.                      settings.modules, settings.tab_groups, settings.fields, settings.layouts, 
  17.                      settings.macros, settings.custom_links, settings.custom_buttons, settings.roles, 
  18.                      settings.profiles 
  19.          modules        modules.all, modules.approvals, modules.leads, modules.accounts, modules.contacts, 
  20.                      modules.deals, modules.campaigns, modules.tasks, modules.cases, modules.events, 
  21.                      modules.calls, modules.solutions, modules.products, modules.vendors, 
  22.                      modules.pricebooks, modules.quotes, modules.salesorders, modules.purchaseorders, 
  23.                      modules.invoices, modules.custom, modules.dashboards, modules.notes, 
  24.                      modules.activities, modules.search 
  25.   
  26.      Possible Module Names 
  27.          leads, accounts, contacts, deals, campaigns, tasks, cases, events, calls, solutions, products, 
  28.          vendors, pricebooks, quotes, salesorders, purchaseorders, invoices, custom, notes, approvals, 
  29.          dashboards, search, activities 
  30.   
  31.  */ 
  32.   
  33.      // Global vars for Zoho API 
  34.      $zoho_apis_com = "https://www.zohoapis.com"
  35.      $zoho_apis_eu = "https://www.zohoapis.eu"
  36.      $refresh_access_token_url = "https://accounts.zoho.eu/oauth/v2/token"
  37.   
  38.      // Endpoint: Sandbox  // disable after testing 
  39.      $zoho_sandbox = "https://sandbox.zohoapis.eu"
  40.      $zoho_sandbox_domain = "https://crmsandbox.zoho.eu/crm"
  41.   
  42.      // Global vars to be used by below functions specific to this app 
  43.      $zoho_client_id = "1000.your_client_id"
  44.      $zoho_client_secret = "your_client_secret"
  45.      $zoho_redirect_uri = "your_redirect_uri";  // will be URL to start.php in this example 
  46.      $access_token_path = "path_to_your_access_token_not_on_www_but_accessible_by_script/access_token.dat"
  47.      $refresh_token_path = "path_to_your_access_token_not_on_www_but_accessible_by_script/refresh_token.dat"
  48.   
  49.      // function to use Zoho API v2 
  50.      function abZohoApi( $post_url, $post_fields, $post_header=false, $post_type='GET' ) 
  51.      { 
  52.          // setup cURL request 
  53.          $ch=curl_init()
  54.   
  55.          // do not return header information 
  56.          curl_setopt($ch, CURLOPT_HEADER, 0)
  57.   
  58.          // submit data in header if specified 
  59.          if(is_array($post_header)){ 
  60.              curl_setopt($ch, CURLOPT_HTTPHEADER, $post_header)
  61.          } 
  62.   
  63.          // do not return status info 
  64.          curl_setopt($ch, CURLOPT_VERBOSE, 0)
  65.   
  66.          // return data 
  67.          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true)
  68.   
  69.          // cancel ssl checks 
  70.          curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false)
  71.          curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false)
  72.   
  73.          // if using GET, POST or PUT 
  74.          if($post_type=='POST'){ 
  75.              curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST')
  76.              curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields)
  77.          } else if($post_type=='PUT'){ 
  78.              curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT')
  79.              curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields))
  80.          } else if($post_type=='DELETE'){ 
  81.              curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE')
  82.          }else{ 
  83.              curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET')
  84.              if($post_fields){ 
  85.                  $post_url.='?'.http_build_query($post_fields)
  86.              } 
  87.          } 
  88.   
  89.          // specified endpoint 
  90.          curl_setopt($ch, CURLOPT_URL, $post_url)
  91.   
  92.          // execute cURL request 
  93.          $response=curl_exec($ch)
  94.   
  95.          // return errors if any 
  96.          if (curl_exec($ch) === false) { 
  97.              $output = curl_error($ch)
  98.          } else { 
  99.              $output = $response
  100.          } 
  101.   
  102.          // close cURL handle 
  103.          curl_close($ch)
  104.   
  105.          // output 
  106.          return $output
  107.      } 
  108.   
  109.      // Generate the refresh token by manually entering the following in the browser 
  110.      // Browse to https://accounts.zoho.eu/oauth/v2/auth?scope=ZohoCRM.modules.ALL,ZohoCRM.users.READ,ZohoCRM.settings.fields.read&client_id=your_client_id&response_type=code&access_type=offline&redirect_uri=your_url_path_to_start_page/start.php&prompt=consent 
  111.   
  112.      // function to generate refresh token from Zoho authorization code 
  113.      function generate_refresh_token(){ 
  114.   
  115.          // use vars declared at beginning of script 
  116.          global $zoho_client_id, $zoho_client_secret, $zoho_redirect_uri, $access_token_path, $refresh_token_path
  117.   
  118.          // Generate Access Token and Refresh Token - Read url GET values 
  119.          $zoho_grant_token = $_GET['code']
  120.          $zoho_location = $_GET['location']
  121.          $zoho_accounts_server = $_GET['accounts-server']
  122.   
  123.          // Generate Access Token and Refresh Token 
  124.          $url_auth=urldecode($zoho_accounts_server)."/oauth/v2/token"
  125.   
  126.          // Build fields to post 
  127.          $fields_token=array("code"=>$zoho_grant_token, "redirect_uri"=>$zoho_redirect_uri, "client_id"=>$zoho_client_id, "client_secret"=>$zoho_client_secret, "grant_type"=>"authorization_code", "prompt"=>"consent")
  128.   
  129.          // Generate Access Token and Refresh Token - post via cURL 
  130.          $response_json = abZohoApi($url_auth, $fields_token, false, 'POST')
  131.   
  132.          // Generate Access Token and Refresh Token - format output (convert JSON to Object) 
  133.          $refresh_token_arr = json_decode($response_json, true)
  134.   
  135.          // store in var 
  136.          $refresh_token = isset($refresh_token_arr['refresh_token']) ? $refresh_token_arr['refresh_token'] : 0
  137.   
  138.          // encode value to base64 
  139.          $refresh_token_base64 = base64_encode($refresh_token)
  140.   
  141.          // store encoded value to file 
  142.          file_put_contents($refresh_token_path, $refresh_token_base64)
  143.   
  144.          // -- do access token while we're here 
  145.          // store in access token 
  146.          $access_token = isset($refresh_token_arr['access_token']) ? $refresh_token_arr['access_token'] : 0
  147.   
  148.          // encode value to base64 
  149.          $access_token_base64 = base64_encode($access_token)
  150.   
  151.          // store encoded value to file 
  152.          file_put_contents($access_token_path, $access_token_base64)
  153.   
  154.          // return array of json objects 
  155.          return $refresh_token_arr
  156.      } 
  157.   
  158.   
  159.      // function to generate access token from refresh token 
  160.      // returns minutes remaining of valid token 
  161.      function generate_access_token(){ 
  162.   
  163.          // use vars declared at beginning of script 
  164.          global $zoho_client_id, $zoho_client_secret, $access_token_path, $refresh_token_path, $refresh_access_token_url
  165.   
  166.          // get refresh token from file 
  167.          $refresh_token = base64_decode( file_get_contents( $refresh_token_path ) )
  168.   
  169.          // build fields to post 
  170.          $refresh_fields = array("refresh_token" => $refresh_token, "client_id" => $zoho_client_id, "client_secret" => $zoho_client_secret, "grant_type" => "refresh_token")
  171.   
  172.          // send to Zoho API 
  173.          $this_access_token_json = abZohoApi($refresh_access_token_url, $refresh_fields, false, 'POST')
  174.   
  175.          // convert JSON response to array 
  176.          $access_token_arr = json_decode($this_access_token_json, true)
  177.   
  178.          // store in var 
  179.          $returned_token = $access_token_arr['access_token']
  180.   
  181.          // encode value to base64 
  182.          $access_token_base64 = base64_encode($returned_token)
  183.   
  184.          // store encoded value to file 
  185.          file_put_contents($access_token_path, $access_token_base64)
  186.      } 
  187.   
  188.      // function to decode and read access token from file 
  189.      function read_token($file){ 
  190.   
  191.          // get access token from file 
  192.          $token_base64 = file_get_contents($file)
  193.   
  194.          // decode value to token 
  195.          $token = base64_decode($token_base64)
  196.   
  197.          // output 
  198.          return $token
  199.      } 
  200.   
  201.      // function to sort our returned data (multidimensional array) 
  202.      function array_sort_by_column(&$arr, $col, $dir = SORT_DESC) { 
  203.          $sort_col = array()
  204.          foreach ($arr as $key=> $row) { 
  205.              $sort_col[$key] = strtolower($row[$col])// strtolower to make it case insensitive 
  206.          } 
  207.          array_multisort($sort_col, $dir, $arr)
  208.      } 
  209.   
  210.      // function to get minutes left on a generated file 
  211.      // defaults to an hour expiry time 
  212.      // usage: get_time_remaining( 'access.dat', 3600) 
  213.      function get_time_remaining($file, $expiry_in_seconds=3600){ 
  214.   
  215.          // get file modified time 
  216.          $file_modified_time = filemtime($file)
  217.   
  218.          // add 1 hour 
  219.          $file_expiry_time = $file_modified_time + $expiry_in_seconds
  220.   
  221.          // calculate seconds left 
  222.          $diff = $file_expiry_time - time()
  223.   
  224.          // round to minutes 
  225.          $minutes = floor($diff/60)
  226.   
  227.          // output 
  228.          return $minutes
  229.      } 
  230.   
  231.   
  232.      // function to check access token and regenerate if necessary 
  233.      function check_access_token(){ 
  234.   
  235.          global $access_token_path
  236.   
  237.          // get time remaining on access token (1 hour max) 
  238.          $access_token_time_remaining = get_time_remaining($access_token_path)
  239.   
  240.          // if less than 5 minutes left, regenerate token 
  241.          if($access_token_time_remaining<=5){ 
  242.   
  243.              // Generate Access Token from Refresh Token 
  244.              generate_access_token()
  245.   
  246.              // update time remaining on access token (again) 
  247.              $access_token_time_remaining = get_time_remaining($access_token_path)
  248.          } 
  249.   
  250.          // return time remaining (in minutes) 
  251.          return $access_token_time_remaining
  252.   
  253.      } 
  254.   
  255.   
  256.      // get data: returns PHP Array (for functional sorting: PHP & JS) 
  257.      // usage: Leads: get_records("Leads") 
  258.      // usage: Lead: get_records("Leads", 98304820934029840) 
  259.      // usage: User: get_records("users", 10825000000119017)  // note that users has to be lowercase 
  260.      function get_records($zoho_category, $zoho_id=0, $fields_data=array()){ 
  261.   
  262.          global $access_token_path, $zoho_apis_eu
  263.   
  264.          // get access token 
  265.          $access_token = read_token($access_token_path)
  266.   
  267.          // endpoint 
  268.          $url_data=$zoho_apis_eu."/crm/v2/".$zoho_category
  269.   
  270.          // if array (eg. Related Records), accept ID and related_list_apiname 
  271.          if(is_array($zoho_id)){ 
  272.              $url_data.= $zoho_id[0]>0 ? '/'.$zoho_id[0].'/'.$zoho_id[1] : ''
  273.          }else{ 
  274.              // add id if exists 
  275.              $url_data.= $zoho_id!=0 && $zoho_id!="" ? '/'.$zoho_id : ''
  276.          } 
  277.   
  278.          // add access token to header 
  279.          $header_data = array("Authorization: Zoho-oauthtoken ".$access_token)
  280.   
  281.          // send to Zoho API 
  282.          $response_json = abZohoApi($url_data, $fields_data, $header_data)
  283.   
  284.          // convert response to PHP array (for sorting) 
  285.          $response_arr = json_decode($response_json, true)
  286.   
  287.          // output 
  288.          return $response_arr
  289.      } 
  290.   
  291.      // get data: returns PHP Array (for functional sorting: PHP & JS) 
  292.      // usage: Leads: search_records("Leads", array('Last_Name:starts_with:G', 'Email:equals:This email address is being protected from spambots. You need JavaScript enabled to view it.')) 
  293.      function search_records($zoho_category, $criteria=array()){ 
  294.   
  295.          global $access_token_path, $zoho_apis_eu
  296.   
  297.          // get access token 
  298.          $access_token = read_token($access_token_path)
  299.   
  300.          // endpoint 
  301.          $url_data=$zoho_apis_eu."/crm/v2/".$zoho_category."/search?criteria="
  302.   
  303.          // join the criteria 
  304.          if(count($criteria)==1){ 
  305.              $url_data.= '('.$criteria[0].')'
  306.          }elseif(count($criteria)>1){ 
  307.              $url_data.= '(('.implode($criteria, ') and (').'))'
  308.          } 
  309.   
  310.          // add access token to header 
  311.          $header_data=array("Authorization: Zoho-oauthtoken ".$access_token)
  312.   
  313.          // send to Zoho API 
  314.          $response_json = abZohoApi($url_data, false, $header_data)
  315.   
  316.          // convert response to PHP array (for sorting) 
  317.          $response_arr = json_decode($response_json, true)
  318.   
  319.          // output 
  320.          return $response_arr
  321.      } 
  322.   
  323.      // function to retrieve current user record 
  324.      // accepts User ID as parameter 
  325.      // returns array( authorized{ok,fail}, name, email, profile, role ) 
  326.      function authenticate_user($user_id){ 
  327.   
  328.          global $access_token_path
  329.   
  330.          // get access token 
  331.          $token = read_token($access_token_path)
  332.   
  333.          // pass parameter requesting only active users who are also confirmed 
  334.          $user_fields = array("type"=>"ActiveConfirmedUsers")
  335.   
  336.          // authenticate user (id was stored in cookie from GET var when this app was initially loaded) 
  337.          $zoho_user_record = get_records("users", $user_id, $user_fields)
  338.   
  339.          // failed by default 
  340.          $user_authorized = 'fail'
  341.          $zoho_user_name=$zoho_user_email=$zoho_user_profile=$zoho_user_role=""
  342.          if(isset($zoho_user_record['users'])){ 
  343.   
  344.              // record is readable 
  345.              $zoho_user_isactive = $zoho_user_record['users'][0]['status']
  346.   
  347.              // if status is active 
  348.              if($zoho_user_isactive=='active'){ 
  349.                  $user_authorized = 'ok'
  350.                  $zoho_user_name = $zoho_user_record['users'][0]['full_name']
  351.   
  352.                  // user email 
  353.                  $zoho_user_email = $zoho_user_record['users'][0]['email']
  354.   
  355.                  // user profile 
  356.                  $zoho_user_profile = $zoho_user_record['users'][0]['profile']['name']
  357.   
  358.                  // user role 
  359.                  $zoho_user_role = $zoho_user_record['users'][0]['role']['name']
  360.              } 
  361.          } 
  362.   
  363.          // return vars 
  364.          return array("authorized"=>$user_authorized, "name"=>$zoho_user_name, "email"=>$zoho_user_email, "profile"=>$zoho_user_profile, "role"=>$zoho_user_role)
  365.      } 
  366.   
  367.   
  368.      // function to ensure data was transferred 
  369.      // accepts array (JSON Response) 
  370.      // returns boolean 
  371.      function check_data_is_valid($data){ 
  372.          $is_valid = false
  373.          if(isset($data['data'])){ 
  374.              $is_valid = true
  375.          } 
  376.          return $is_valid
  377.      } 

Then we need a start page that the user will browse to (or that is the endpoint for the redirect), I will call this file start.php:
copyraw
<?php

	// set header
    header('Content-Type: text/html; charset=utf-8');

	// include global functions
	include('./functions.php');
?>
<!doctype html>
<html>
    <head>
        <title>Zoho OAuth Script</title>
    </head>
    <body>
<?php
    // check access token, regenerate if expired (1 hour)
    check_access_token();

    // determine minutes left
    $access_token_time_remaining = get_time_remaining($access_token_path);

    // generate another token if about to expire
    if($access_token_time_remaining<5){
        echo '<h1>Oops! Something went wrong.</h1>';
        generate_access_token();
        echo '<p><b><u>Access</u></b> Token has been regenerated.  Please reload this page.</p><pre>';

        // get access token from file
        $access_token = base64_decode( file_get_contents( $access_token_path ) );

        // display access token
        echo "\t".$access_token;
        echo '</pre>';
    }else{
        echo '<h1>Yay! All went well.</h1>';
        echo '<p>Stored <b><u>Access</u></b> Token is valid for another '.$access_token_time_remaining.' minute'.($access_token_time_remaining==1?'':'s').'.</p><pre>';

        // get access token from file
        $access_token = base64_decode( file_get_contents( $access_token_path ) );

        // display access token
        echo "\t".$access_token;
        echo '</pre>';
    }

    // if refresh token is being generated
    if(isset($_GET['code'])){

        // read get vars (code) generate refresh and access token.  Store refresh token in file.
        $this_response_arr = generate_refresh_token();

        // get refresh token from file
        $refresh_token = base64_decode( file_get_contents( $refresh_token_path ) );

        // check refresh token exists and is of expected length
        if(strlen($refresh_token)==70){
            echo '<h1>Yay! All went well.</h1>';
            echo '<p><b>Refresh</b> Token successfully generated and stored.</p><pre>';
            print_r($this_response_arr);
            echo '</pre>';
        }else{
            echo '<h1>Oops! Something went wrong.</h1>';
            echo '<p><b>Refresh</b> token was not regenerated.</p><pre>';
            print_r($this_response_arr);
            echo '</pre>';
        }

    }

?>
        <br />
        PHP Code to get all <b><u>Leads</u></b>:<br />
        <pre>
        $all_lead_records = get_records("Leads");
        print_r( $all_lead_records );
<?php
//        $all_lead_records = get_records("Leads");
//        print_r( $all_lead_records['data'][0] );
?>
        </pre>
        <br />
        PHP Code to get a specific <b><u>Lead</u></b>:<br />
        <pre>
        $this_lead_record = get_records("Leads", "78290000004647043");
        print_r( $this_lead_record );
<?php
        // if no lead exists with this ID then this will return blank
//        $this_lead_record = get_records("Leads", "78290000004647043");
//        print_r($this_lead_record);
?>
        </pre>
        <br />
        PHP Code to update or insert a <b><u>Lead</u></b> record in ZohoCRM:<br />
        <pre>
        // set lead name
        $data_array['data'][0]['First_Name']                = json_encode("John");
        $data_array['data'][0]['Last_Name']                 = json_encode("Smith");

        // build JSON post request
        $data_array['data'][0]['Email']                     = json_encode("This email address is being protected from spambots. You need JavaScript enabled to view it.");
        $data_array['data'][0]['Mobile']                    = json_encode("+44 1234 567 890");

        // merge to a JSON array to post
        $data_json          = json_encode($data_array);

        // prepare cURL variables
        $access_token       = base64_decode( file_get_contents( $access_token_path ) );
        $zoho_target_url    = $zoho_apis_eu . '/crm/v2/Leads/upsert';
        $zoho_header        = array('Authorization: Zoho-oauthtoken '.$access_token, 'Content-Type: application/json');

        // just do it
        $response_json      = abZohoApi($zoho_target_url, $data_json, $zoho_header, 'POST');

        // output response (optional)
        echo $response_json;
        </pre>
    </body>
</html>
  1.  <?php 
  2.   
  3.      // set header 
  4.      header('Content-Type: text/html; charset=utf-8')
  5.   
  6.      // include global functions 
  7.      include('./functions.php')
  8.  ?> 
  9.  <!doctype html> 
  10.  <html> 
  11.      <head> 
  12.          <title>Zoho OAuth Script</title> 
  13.      </head> 
  14.      <body> 
  15.  <?php 
  16.      // check access token, regenerate if expired (1 hour) 
  17.      check_access_token()
  18.   
  19.      // determine minutes left 
  20.      $access_token_time_remaining = get_time_remaining($access_token_path)
  21.   
  22.      // generate another token if about to expire 
  23.      if($access_token_time_remaining<5){ 
  24.          echo '<h1>Oops! Something went wrong.</h1>'
  25.          generate_access_token()
  26.          echo '<p><b><u>Access</u></b> Token has been regenerated.  Please reload this page.</p><pre>'
  27.   
  28.          // get access token from file 
  29.          $access_token = base64_decode( file_get_contents( $access_token_path ) )
  30.   
  31.          // display access token 
  32.          echo "\t".$access_token
  33.          echo '</pre>'
  34.      }else{ 
  35.          echo '<h1>Yay! All went well.</h1>'
  36.          echo '<p>Stored <b><u>Access</u></b> Token is valid for another '.$access_token_time_remaining.' minute'.($access_token_time_remaining==1?'':'s').'.</p><pre>'
  37.   
  38.          // get access token from file 
  39.          $access_token = base64_decode( file_get_contents( $access_token_path ) )
  40.   
  41.          // display access token 
  42.          echo "\t".$access_token
  43.          echo '</pre>'
  44.      } 
  45.   
  46.      // if refresh token is being generated 
  47.      if(isset($_GET['code'])){ 
  48.   
  49.          // read get vars (code) generate refresh and access token.  Store refresh token in file. 
  50.          $this_response_arr = generate_refresh_token()
  51.   
  52.          // get refresh token from file 
  53.          $refresh_token = base64_decode( file_get_contents( $refresh_token_path ) )
  54.   
  55.          // check refresh token exists and is of expected length 
  56.          if(strlen($refresh_token)==70){ 
  57.              echo '<h1>Yay! All went well.</h1>'
  58.              echo '<p><b>Refresh</b> Token successfully generated and stored.</p><pre>'
  59.              print_r($this_response_arr)
  60.              echo '</pre>'
  61.          }else{ 
  62.              echo '<h1>Oops! Something went wrong.</h1>'
  63.              echo '<p><b>Refresh</b> token was not regenerated.</p><pre>'
  64.              print_r($this_response_arr)
  65.              echo '</pre>'
  66.          } 
  67.   
  68.      } 
  69.   
  70.  ?> 
  71.          <br /> 
  72.          PHP Code to get all <b><u>Leads</u></b>:<br /> 
  73.          <pre> 
  74.          $all_lead_records = get_records("Leads")
  75.          print_r( $all_lead_records )
  76.  <?php 
  77.  //        $all_lead_records = get_records("Leads")
  78.  //        print_r( $all_lead_records['data'][0] )
  79.  ?> 
  80.          </pre> 
  81.          <br /> 
  82.          PHP Code to get a specific <b><u>Lead</u></b>:<br /> 
  83.          <pre> 
  84.          $this_lead_record = get_records("Leads", "78290000004647043")
  85.          print_r( $this_lead_record )
  86.  <?php 
  87.          // if no lead exists with this ID then this will return blank 
  88.  //        $this_lead_record = get_records("Leads", "78290000004647043")
  89.  //        print_r($this_lead_record)
  90.  ?> 
  91.          </pre> 
  92.          <br /> 
  93.          PHP Code to update or insert a <b><u>Lead</u></b> record in ZohoCRM:<br /> 
  94.          <pre> 
  95.          // set lead name 
  96.          $data_array['data'][0]['First_Name']                = json_encode("John")
  97.          $data_array['data'][0]['Last_Name']                 = json_encode("Smith")
  98.   
  99.          // build JSON post request 
  100.          $data_array['data'][0]['Email']                     = json_encode("This email address is being protected from spambots. You need JavaScript enabled to view it.")
  101.          $data_array['data'][0]['Mobile']                    = json_encode("+44 1234 567 890")
  102.   
  103.          // merge to a JSON array to post 
  104.          $data_json          = json_encode($data_array)
  105.   
  106.          // prepare cURL variables 
  107.          $access_token       = base64_decode( file_get_contents( $access_token_path ) )
  108.          $zoho_target_url    = $zoho_apis_eu . '/crm/v2/Leads/upsert'
  109.          $zoho_header        = array('Authorization: Zoho-oauthtoken '.$access_token, 'Content-Type: application/json')
  110.   
  111.          // just do it 
  112.          $response_json      = abZohoApi($zoho_target_url, $data_json, $zoho_header, 'POST')
  113.   
  114.          // output response (optional) 
  115.          echo $response_json
  116.          </pre> 
  117.      </body> 
  118.  </html> 

Additional Notes
Note that in the above, the tokens are stored in Base64 encoded strings. You could add an actual encryption method (recommended) but for simplicity I'm using the built-in base64_encode function.

Update 2020

Please read the above article to understand the basic process which is:
  1. Register app to generate client ID/secret
  2. Get Grant Token (value of variable called "Code")
  3. Accept permissions of app with your Zoho login
  4. Use refresh token to generate access token
My quickest solution to this is simply to upload the following 2 files to any webserver (doesn't have to be the client's who will be using this API).

Redirect URI file
I give the web URL to this file as the redirect_uri value in a token request. This redirect simply forwards all the received data (via GET method) on to our main script which will do everything else.
copyraw
<?php
	header('Location: https://localhost/get_tokens.php?' . http_build_query($_GET));
?>
  1.  <?php 
  2.      header('Location: https://localhost/get_tokens.php?' . http_build_query($_GET))
  3.  ?> 

Get Tokens file
Not all of what this file does is necessary. I'm just posting the entire file of what I use. It doesn't have any identifying or data-sensitive info, speaking of which, you can remove the info outputs as I also use this script to explain OAuth2.0 and getting access to a ZohoCRM.

As an overview it does the following:
  1. Displays a HTML form for you to select your datacenter (EU / COM / COM.CN / etc)
  2. Displays a HTML form for you to enter the client ID, client secret, scope(s), redirect url
  3. Submit form and receive the CODE variable from the URL (GET method)
  4. You get redirected to an app permissions page where you need to login with the Zoho account that belongs to the organization and can authorize this app.
  5. The script will then display the refresh_token and uses this to generate an access_token value.
  6. Uses the access_token and gets the last 2 contact records added to your ZohoCRM.
copyraw
<?php
/*
	---------------------------------------------------------------------------
	Zoho Authorization via oAuth2.0 for REST API v2
	---------------------------------------------------------------------------

    Zoho API v2:	https://accounts.zoho.eu/developerconsole
                                https://accounts.zoho.com/developerconsole

	Documentation:	https://www.zoho.com/crm/help/api/v2/

	Available Scopes
	    users           users.all
	    org		org.all
	    settings	settings.all, settings.territories, settings.custom_views, settings.related_lists,
	    			settings.modules, settings.tab_groups, settings.fields, settings.layouts,
	    			settings.macros, settings.custom_links, settings.custom_buttons, settings.roles,
	    			settings.profiles
	    modules	modules.all, modules.approvals, modules.leads, modules.accounts, modules.contacts,
	    			modules.deals, modules.campaigns, modules.tasks, modules.cases, modules.events,
	    			modules.calls, modules.solutions, modules.products, modules.vendors,
	    			modules.pricebooks, modules.quotes, modules.salesorders, modules.purchaseorders,
	    			modules.invoices, modules.custom, modules.dashboards, modules.notes,
	    			modules.activities, modules.search

	Possible Module Names
		leads, accounts, contacts, deals, campaigns, tasks, cases, events, calls, solutions, products, vendors, pricebooks, quotes, salesorders, purchaseorders, invoices, custom, notes, approvals,	dashboards, search,activities
*/
header("Content-Type: text/html");
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// init
$b_Sandbox = false;
$v_AccessTokenPath = $v_RefreshTokenPath = "";
$a_CheckFields = $a_GrantFields = $a_Header = $a_Payload = array();
$v_RootFolder = "../App_Data/ab";  // must be writeable by script (and inaccessible by public)
$v_TmpFolder = $v_RootFolder . "/tmp";
$v_DelFolder = $v_TmpFolder . "/delete_me";
$v_WorkingFolder = $v_TmpFolder;
$v_TmpFile = "grant_info_" . date("YmdH") . ".dat";
$v_Protocol = $_SERVER['HTTPS']=="on" ? "https" : "http";
$v_RedirectUri = $v_Protocol . "://" . $_SERVER['SERVER_NAME'] . "/zohoapi/redirect_uri.php";
$v_Separator = "<hr style='border:3px solid #eee;' />";

// used for data samples
$v_CrmEndpoint = ($b_Sandbox) ? "https://sandbox.zohoapis." . $v_TLD . "/crm/v2" : "https://www.zohoapis." . $v_TLD . "/crm/v2";
$v_DataEndpoint = "https://inventory.zoho.com/api/v1/items?organization_id=012345678"; // used to get sample data

// create temp folder if not exists
if (!file_exists($v_TmpFolder)) {
    mkdir($v_TmpFolder, 0777, true);
}
if (!file_exists($v_DelFolder)) {
    mkdir($v_DelFolder, 0777, true);
}

// get top level domain
if(isset($_GET['location'])){
    switch($_GET['location']){
        case "us":
            $v_TLD = "com";
            break;
        case "eu":
            $v_TLD = "eu";
            break;
        default:
            $v_TLD = "com";
    }
}else{
    $v_TLD = isset($_GET['tld']) ? strtolower($_GET['tld']) : "none";
}

// if client id in url then get and store
if(isset($_GET['client_id'])){
    if($_GET['client_id'] != ""){
        file_put_contents($v_TmpFolder . "/" . $v_TmpFile, json_encode($_GET, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    }
}

// begin HTML output
echo "<html><head><title>JoelLipman - API v2 Tokenizer (" . strtoupper($v_TLD) . ")</title><style>";
$v_AppStyle = "body{font-family:Arial,Verdana,Sans-serif;background-color:#ccc;}select,option,input.txt{padding:5px 10px;outline:0;border:1px solid#ccc;border-radius:5px;}input.btn{padding:10px 20px;outline:0;color:#fff;background:#10bc83;border-radius:2px;border:0;-webkit-box-shadow:2px 2px 2px 0px rgba(51,51,51,0.3);-moz-box-shadow:2px 2px 2px 0px rgba(51,51,51,0.3);box-shadow:2px 2px 2px 0px rgba(51,51,51,0.3);cursor:pointer;}input.txt{width:400px;}form{margin:20px;}h3{margin:0;}select{float:right;}td{padding:2px 5px;}label{display:inline-block;width:100px;}div.panel{background-color:#fff;border-radius:10px;margin:20px 10px;padding:20px 10px;-webkit-box-shadow:5px 5px 5px 0px rgba(51,51,51,0.3);-moz-box-shadow:5px 5px 5px 0px rgba(51,51,51,0.3);box-shadow:5px 5px 5px 0px rgba(51,51,51,0.3);}form{margin:0;padding:0;}#copyright{bottom:0;margin:0 auto;text-align:center;width:97%;margin-bottom:10px;}#copyright a{color:#999;text-decoration:none;font-size:10pt;line-height:20px;}#menu_bottom a{color:#f00;text-decoration:none;bottom:30;font-size:10pt;line-height:20px;}.centered{text-align:center;margin:0 auto;}h5{color:#999;font-weight:100;font-size:10pt;margin-bottom:5px;margin-top:10px;}tt.source{color:#fff;background-color:#000;}tt.target{background-color:yellow;}tt.bold{color:red;font-weight:700;}ul{margin:10px 0 0 85px;font-size:75%;color:#666;padding:0;}pre{margin:0;}";
$v_AppStyleFormatted = trim(preg_replace('/\s+/', ' ', $v_AppStyle));
$a_ReplaceFrom1 = array("px ", "0 ", " a");
$a_ReplaceTo1 = array("px?", "0?", "?a");
$v_AppStyleFormatted = str_replace($a_ReplaceFrom1, $a_ReplaceTo1, $v_AppStyleFormatted);
$a_ReplaceFrom2 = array(" ", "?");
$a_ReplaceTo2 = array("", " ");
$v_AppStyleFormatted = str_replace($a_ReplaceFrom2, $a_ReplaceTo2, $v_AppStyleFormatted);
echo $v_AppStyleFormatted . "</style></head><body>";
if((isset($_GET['tld']) || isset($_GET['location']))&&(file_exists($v_TmpFolder . "/" . $v_TmpFile))){
    //
    // 0. initialize vars
    //
    echo "<div class='panel'>";
    echo "<h3>0. INITIALIZE...</h3>";
    echo "<h5>Info:</h5>";
    $v_AuthEndpoint = "https://accounts.zoho." . $v_TLD . "/oauth/v2/auth";
    $v_TokenEndpoint = "https://accounts.zoho." . $v_TLD . "/oauth/v2/token";
    if(file_exists($v_TmpFolder . "/" . $v_TmpFile) && isset($_GET['code'])){
        $m_GrantInfo = json_decode(file_get_contents($v_TmpFolder . "/" . $v_TmpFile), true);
        $a_CheckFields['client_id'] = $m_GrantInfo['client_id'];
        $a_CheckFields['client_secret'] = $m_GrantInfo['client_secret'];
        $a_CheckFields['scopes'] = $m_GrantInfo['scopes'];
        $a_CheckFields['tld'] = $m_GrantInfo['tld'];
        $a_CheckFields['redirect_uri'] = $m_GrantInfo['redirect_uri'];
        rename($v_TmpFolder . "/" . $v_TmpFile, $v_DelFolder . "/" . $v_TmpFile);
    }else{
        $a_CheckFields['client_id'] = $_GET['client_id'];
        $a_CheckFields['client_secret'] = $_GET['client_secret'];
        $a_CheckFields['scopes'] = $_GET['scopes'];
        $a_CheckFields['tld'] = $_GET['tld'];
        $a_CheckFields['redirect_uri'] = $_GET['redirect_uri'];
    }
    $a_InitFields['tld'] = $a_CheckFields['tld'];
    $a_InitFields['client_id'] = $a_CheckFields['client_id'];
    $a_InitFields['client_secret'] = $a_CheckFields['client_secret'];
    $a_InitFields['scopes'] = $a_CheckFields['scopes'];
    $a_InitFields['redirect_uri'] = $a_CheckFields['redirect_uri'];
    $a_Info = array();
    $a_Info['api']['endpoint'] = $v_AuthEndpoint;
    $a_Info['api']['header'] = false;
    $a_Info['api']['ssl_check'] = false;
    $a_Info['sandbox'] = $b_Sandbox ? "<tt class='bold'>true</tt>" : "<tt class='bold'>false</tt>";
    echo "<pre>" . json_encode(array_merge($a_InitFields,$a_Info), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
    //
    // 1. get grant token
    //
    echo "</div><div class='panel'>";
    echo "<h3>1. GRANT TOKEN...</h3>";
    if($a_CheckFields['client_id']!="")
    {
        $v_GrantTokenPath = $v_RootFolder . $a_CheckFields['client_id'] . "/grant_token.dat";
        $v_RefreshTokenPath = $v_RootFolder . $a_CheckFields['client_id'] . "/refresh_token.dat";
        $v_AccessTokenPath = $v_RootFolder . $a_CheckFields['client_id'] . "/access_token.dat";
        $v_WorkingFolder = $v_RootFolder . $a_CheckFields['client_id'];
        if (!file_exists($v_WorkingFolder)) {
            mkdir($v_WorkingFolder, 0777, true);
        }
    }
    echo "<h5>Info:</h5>";
    $a_Info = array();
    $a_Info['api']['endpoint'] = $v_AuthEndpoint;
    $a_Info['api']['method'] = "GET";
    $a_Info['api']['header'] = false;
    $a_Info['api']['ssl_check'] = false;
    $a_Info['expires'] = "<tt class='bold'>" . "in 1 to 10 minutes" . "</tt>";
    $a_Info['date_created'] = file_exists($v_GrantTokenPath) ? date("Y-m-d H:i:s P", filectime($v_GrantTokenPath)) : date("Y-m-d H:i:s P");
    $a_Info['date_modified'] = file_exists($v_GrantTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_GrantTokenPath)) : date("Y-m-d H:i:s P");
    $a_Info['date_expires'] = file_exists($v_GrantTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_GrantTokenPath) + 300) : date("Y-m-d H:i:s P", strtotime(time() + 300));
    echo "<pre>" . json_encode($a_Info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
    echo "<h5>Request (GET):</h5>";
    $a_Request = array();
    $a_Request['client_id'] = $a_CheckFields['client_id'];
    $a_Request['redirect_uri'] = $a_CheckFields['redirect_uri'];
    $a_Request['scope'] = $a_CheckFields['scopes'];
    $a_Request['response_type'] = "code";
    $a_Request['access_type'] = "offline";
    $a_Request['prompt'] = "consent";
    $a_Request['state'] = "testing";
    echo "<pre>" . json_encode($a_Request, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
    $v_GrantTokenUrl = $v_AuthEndpoint . "?" . http_build_query($a_Request);
    if(isset($_GET['code']))
    {
        echo "<h5>Response (GET):</h5>";
        $a_Response = array();
        $a_Response['state'] = $_GET['state'];
        $a_Response['code'] = $_GET['code'];
        $a_Response['location'] = $_GET['location'];
        $a_Response['accounts-server'] = $_GET['accounts-server'];
        if($v_GrantTokenPath != "" && $b_Sandbox){
            file_put_contents($v_GrantTokenPath, json_encode($a_Response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
        }
        foreach($a_Response as $v_Key => $v_Value){
            if($v_Key == "code"){
                $a_Response['code'] = "<tt class='source'>" . $v_Value . "</tt>";
            }
        }
        echo "<pre>" . json_encode($a_Response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        //
        // 2. get refresh token
        //
        echo "</div><div class='panel'>";
        echo "<h3>2. REFRESH TOKEN...</h3>";
        echo "<h5>Info:</h5>";
        $a_Info = array();
        $a_Info['api']['endpoint'] = $v_TokenEndpoint;
        $a_Info['api']['method'] = "POST";
        $a_Info['api']['header'] = false;
        $a_Info['api']['ssl_check'] = false;
        $a_Info['expires'] = "<tt class='bold'>" . "Unless revoked or overwritten, a refresh token NEVER expires." . "</tt>";
        $a_Info['date_created'] = file_exists($v_RefreshTokenPath) ? date("Y-m-d H:i:s P", filectime($v_RefreshTokenPath)) : date("Y-m-d H:i:s P");
        $a_Info['date_modified'] = file_exists($v_RefreshTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_RefreshTokenPath)) : date("Y-m-d H:i:s P");
        echo "<pre>" . json_encode($a_Info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        echo "<h5>Request (POST):</h5>";
        $a_Payload = array();
        $a_Payload['code'] = $_GET['code'];
        $a_Payload['client_id'] = $a_CheckFields['client_id'];
        $a_Payload['client_secret'] = $a_CheckFields['client_secret'];
        $a_Payload['redirect_uri'] = $a_CheckFields['redirect_uri'];
        $a_Payload['grant_type'] = "authorization_code";
        $h_Curl=curl_init();
        $a_CurlOptions = array(
            CURLOPT_URL => $v_TokenEndpoint,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => $a_Payload,
            CURLOPT_HEADER => 0,
            CURLOPT_VERBOSE => 0,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_SSL_VERIFYPEER => 0,
            CURLOPT_SSL_VERIFYHOST => 0
        );
        curl_setopt_array($h_Curl, $a_CurlOptions);
        $v_RefreshCurl = curl_exec($h_Curl);
        curl_close($h_Curl);
        foreach($a_Payload as $v_Key => $v_Value){
            if($v_Key == "code"){
                $a_Payload['code'] = "<tt class='target'>" . $v_Value . "</tt>";
            }
        }
        echo "<pre>" . json_encode($a_Payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        echo "<h5>Response (POST):</h5>";
        $a_RefreshResponse = json_decode($v_RefreshCurl, true);
        $v_RefreshToken = "ERROR";
        if($v_RefreshTokenPath != ""){
            file_put_contents($v_RefreshTokenPath, json_encode($a_RefreshResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
        }
        if(isset($a_RefreshResponse['refresh_token']))
        {
            $v_RefreshToken = $a_RefreshResponse['refresh_token'];
            foreach($a_RefreshResponse as $v_Key => $v_Value){
                if($v_Key == "refresh_token"){
                    $a_RefreshResponse['refresh_token'] = "<tt class='source'>" . $v_Value . "</tt>";
                }
            }
            echo "<pre>" . json_encode($a_RefreshResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        }else{
            echo "<pre>ERROR: Check your submitted variables are the same: Client ID, Client Secret, Redirect URI, Scope(s)</pre>";
        }
        //
        // 3. get access token
        //
        echo "</div><div class='panel'>";
        echo "<h3>3. ACCESS TOKEN...</h3>";
        echo "<h5>Info:</h5>";
        $a_Info = array();
        $a_Info['api']['endpoint'] = $v_TokenEndpoint;
        $a_Info['api']['method'] = "POST";
        $a_Info['api']['header'] = false;
        $a_Info['api']['ssl_check'] = false;
        $a_Info['expires'] = "<tt class='bold'>" . "in 1 hour after creation" . "</tt>";
        $a_Info['date_created'] = file_exists($v_AccessTokenPath) ? date("Y-m-d H:i:s P", filectime($v_AccessTokenPath)) : date("Y-m-d H:i:s P");
        $a_Info['date_modified'] = file_exists($v_AccessTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_AccessTokenPath)) : date("Y-m-d H:i:s P");
        $a_Info['date_expires'] = file_exists($v_AccessTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_AccessTokenPath) + 3600) : date("Y-m-d H:i:s P", strtotime(time() + 3600));
        echo "<pre>" . json_encode($a_Info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        echo "<h5>Request (POST):</h5>";
        $a_Payload = array();
        $a_Payload['refresh_token'] = $v_RefreshToken;
        $a_Payload['client_id'] = $a_CheckFields['client_id'];
        $a_Payload['client_secret'] = $a_CheckFields['client_secret'];
        $a_Payload['redirect_uri'] = $a_CheckFields['redirect_uri'];
        $a_Payload['grant_type'] = "refresh_token";
        $h_Curl=curl_init();
        $a_CurlOptions = array(
            CURLOPT_URL => $v_TokenEndpoint,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => $a_Payload,
            CURLOPT_HEADER => 0,
            CURLOPT_VERBOSE => 0,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_SSL_VERIFYPEER => 0,
            CURLOPT_SSL_VERIFYHOST => 0
        );
        curl_setopt_array($h_Curl, $a_CurlOptions);
        $v_AccessCurl = curl_exec($h_Curl);
        curl_close($h_Curl);
        $a_AccessResponse = json_decode($v_AccessCurl, true);
        if($v_AccessTokenPath != ""){
            file_put_contents($v_AccessTokenPath, json_encode($a_AccessResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
        }
        foreach($a_Payload as $v_Key => $v_Value){
            if($v_Key == "refresh_token"){
                $a_Payload['refresh_token'] = "<tt class='target'>" . $v_Value . "</tt>";
            }
        }
        echo "<pre>" . json_encode($a_Payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        echo "<h5>Response (POST):</h5>";
        if(isset($a_AccessResponse['access_token'])){
            $v_AccessToken = $a_AccessResponse['access_token'];
            foreach($a_AccessResponse as $v_Key => $v_Value){
                if($v_Key == "access_token"){
                    $a_AccessResponse['access_token'] = "<tt class='source'>" . $v_Value . "</tt>";
                }
            }
            echo "<pre>" . json_encode($a_AccessResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        }else{
            echo "<pre>ERROR: Check the Refresh Token</pre>";
        }
        //
        // 4. get data sample (last 2 records)
        //
        echo "</div><div class='panel'>";
        echo "<h3>4. DATA SAMPLE...</h3>";
        echo "<h5>Info:</h5>";
        $a_Info = array();
        $a_Info['api']['endpoint'] = $v_DataEndpoint;
        $a_Info['api']['method'] = "GET";
        $a_Info['api']['header'] = true;
        $a_Info['api']['ssl_check'] = false;
        $a_Info['date'] = date("Y-m-d H:i:s P");
        echo "<pre>" . json_encode($a_Info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        echo "<h5>Request (GET):</h5>";
        $a_Header = array();
        $a_Header[] = "Authorization: Zoho-oauthtoken " . $v_AccessToken;
        $a_Header[] = "Content-Type: application/x-www-form-urlencoded;charset=UTF-8";
        $a_Payload = array();
        $a_Payload['page'] = 1;
        $a_Payload['per_page'] = 2;
        $a_Payload['sort_by'] = "id";
        $a_Payload['sort_order'] = "D";  // in CRM this is "asc" or "desc".  in books/inventory this is either "A" or "D"
        $a_Request = array("header" => $a_Header, "parameters" => $a_Payload);
        $h_Curl=curl_init();
        $a_CurlOptions = array(
            CURLOPT_URL => $v_DataEndpoint . "&" . http_build_query($a_Payload),
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_HEADER => 1,
            CURLOPT_HTTPHEADER => $a_Header,
            CURLOPT_VERBOSE => 0,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_SSL_VERIFYPEER => 0,
            CURLOPT_SSL_VERIFYHOST => 0
        );
        curl_setopt_array($h_Curl, $a_CurlOptions);
        $v_DataCurl = curl_exec($h_Curl);
        foreach($a_Request as $v_Key => $v_Value){
            if($v_Key == 0){
                $a_Request['header'][0] = str_replace("Zoho-oauthtoken ","Zoho-oauthtoken <tt class='target'>", $a_Request['header'][0]) . "</tt>";
            }
        }
        echo "<pre>" . json_encode($a_Request, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        echo "<h5>Response (GET):</h5>";
        if(stripos($v_DataCurl, '"code"')>0){
            $v_DataStart = stripos($v_DataCurl, '{"code"');
            $v_DataStr = trim(substr($v_DataCurl, $v_DataStart));
            $a_DataResponse = json_decode($v_DataStr, true);
            $a_WriteJson['response'] = $a_DataResponse;
            echo "<pre>" . json_encode($a_WriteJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        }elseif(stripos($v_DataCurl, '"data"')>0){
            $v_DataStart = stripos($v_DataCurl, '{"data"');
            $v_DataStr = trim(substr($v_DataCurl, $v_DataStart));
            $a_DataResponse = json_decode($v_DataStr, true);
            $v_Index = 0;
            foreach($a_DataResponse['data'] as $a_Contact){
                $a_WriteJson['records'][$v_Index]['id'] = $a_Contact['id'];
                $a_WriteJson['records'][$v_Index]['name'] = $a_Contact['Full_Name'];
                $a_WriteJson['records'][$v_Index]['email'] = $a_Contact['Email'];
                $a_WriteJson['records'][$v_Index]['created_time'] = str_replace("+", " +", str_replace("T", " ", $a_Contact['Created_Time']));
                $v_Index++;
            }
            echo "<pre>" . json_encode($a_WriteJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>";
        }else{
            echo "<pre>" . $v_DataCurl . "</pre>";
        }
        echo "</div>";
    }
    else
    {
        echo "<br /><form>";
        echo '<input type="button" value="Click here to open App Permissions »" onclick="javascript:top.location.href=\'' . $v_GrantTokenUrl . '\';" class="btn" style="margin-left: 70px;" />';
        echo "</form>
        <ul>
        <li>Login as a Zoho User with access to the API</li>
        <li>Read and click on accept to allow the app access.</li>
        <li>There is a limit of 5 refresh tokens per minute.</li>
        <li>There is a limit of 19 refresh tokens per app.</li>
        <li>The 20th refresh token will overwrite the 1st.</li>
        </ul><br />";
    }

    // END
    echo "</div><div class='panel'>";
    echo "<h5>Script finished.</h5>";
    echo "<div id='menu_bottom' style='position:normal'><a href='./get_tokens.php' target='_top'>» Start Over</a></div></div>";
    echo "<div id='copyright' style='position:normal'><a href='//joellipman.com/' target='_top'>Copyright © ".date("Y")." Joel Lipman Ltd</a></div></div>";
}else{
    $v_TldSelected = "";
    $a_TldOptions = array('com','com.au','com.cn','eu','in');
    echo "
<div class='panel'><form method='get' action='get_tokens.php'>
    <h3>Configure App</h3>
    <table>
    <tr><td colspan='2'>Please select the domain your ZohoCRM is on:<br /><i style='font-size:75%;'>(eg. https://crm.zoho.com = COM, https://crm.zoho.eu = EU)</i>   <select name='tld' id='tldSelect'>";
    foreach($a_TldOptions as $v_Option){
        $v_SelectedStr = (strtolower($v_TLD) == $v_Option) ? " selected='true'" : "";
        echo "<option value='" . strtolower($v_Option) . "'" . $v_SelectedStr . ">" . strtoupper($v_Option) . "</option>";
    }
    echo "
    </select></td></tr>
    <tr><td colspan='2' class='centered'><input type='button' value='Open Zoho App Registration' onclick='javascript:openAppConsole();' class='btn' /></td></tr>
    <tr><td colspan='2'> </td></tr>
    <tr><td>Client ID</td><td><input name='client_id' value='' class='txt' onclick='this.select();' /></td></tr>
    <tr><td>Client Secret</td><td><input name='client_secret' value='' class='txt' onclick='this.select();' /></td></tr>
    <tr><td>Scopes</td><td><input name='scopes' value='ZohoCRM.modules.ALL,ZohoCRM.settings.READ,ZohoCRM.users.READ' class='txt' onclick='this.select();' /></td></tr>
    <tr><td>Redirect URI</td><td><input name='redirect_uri' value='" . $v_RedirectUri . "' class='txt' onclick='this.select();' /></td></tr>
    <tr><td colspan='2' style='text-align:center;'><input type='submit' value='Get Grant Token' class='btn' /></td></tr>
    </table>
</form></div>
<script>
function openAppConsole() {
    var e = document.getElementById('tldSelect');
    var strOption = e.options[e.selectedIndex].value;
    window.open('https://accounts.zoho.' + strOption + '/developerconsole', '_blank', 'location=yes,height=600,width=800,scrollbars=yes,status=yes');
}
</script>
";
    echo "<div id='copyright' style='position:fixed'><a href='//joellipman.com/' target='_top'>Copyright © ".date("Y")." Joel Lipman Ltd</a></div>";
}
echo "</body></html>";
  1.  <?php 
  2.  /* 
  3.      --------------------------------------------------------------------------- 
  4.      Zoho Authorization via oAuth2.0 for REST API v2 
  5.      --------------------------------------------------------------------------- 
  6.   
  7.      Zoho API v2:    https://accounts.zoho.eu/developerconsole 
  8.                                  https://accounts.zoho.com/developerconsole 
  9.   
  10.      Documentation:    https://www.zoho.com/crm/help/api/v2/ 
  11.   
  12.      Available Scopes 
  13.          users           users.all 
  14.          org        org.all 
  15.          settings    settings.all, settings.territories, settings.custom_views, settings.related_lists, 
  16.                      settings.modules, settings.tab_groups, settings.fields, settings.layouts, 
  17.                      settings.macros, settings.custom_links, settings.custom_buttons, settings.roles, 
  18.                      settings.profiles 
  19.          modules    modules.all, modules.approvals, modules.leads, modules.accounts, modules.contacts, 
  20.                      modules.deals, modules.campaigns, modules.tasks, modules.cases, modules.events, 
  21.                      modules.calls, modules.solutions, modules.products, modules.vendors, 
  22.                      modules.pricebooks, modules.quotes, modules.salesorders, modules.purchaseorders, 
  23.                      modules.invoices, modules.custom, modules.dashboards, modules.notes, 
  24.                      modules.activities, modules.search 
  25.   
  26.      Possible Module Names 
  27.          leads, accounts, contacts, deals, campaigns, tasks, cases, events, calls, solutions, products, vendors, pricebooks, quotes, salesorders, purchaseorders, invoices, custom, notes, approvals,    dashboards, search,activities 
  28.  */ 
  29.  header("Content-Type: text/html")
  30.  ini_set('display_errors', 1)
  31.  ini_set('display_startup_errors', 1)
  32.  error_reporting(E_ALL)
  33.   
  34.  // init 
  35.  $b_Sandbox = false
  36.  $v_AccessTokenPath = $v_RefreshTokenPath = ""
  37.  $a_CheckFields = $a_GrantFields = $a_Header = $a_Payload = array()
  38.  $v_RootFolder = "../App_Data/ab";  // must be writeable by script (and inaccessible by public) 
  39.  $v_TmpFolder = $v_RootFolder . "/tmp"
  40.  $v_DelFolder = $v_TmpFolder . "/delete_me"
  41.  $v_WorkingFolder = $v_TmpFolder
  42.  $v_TmpFile = "grant_info_" . date("YmdH") . ".dat"
  43.  $v_Protocol = $_SERVER['HTTPS']=="on" ? "https" : "http"
  44.  $v_RedirectUri = $v_Protocol . "://" . $_SERVER['SERVER_NAME'] . "/zohoapi/redirect_uri.php"
  45.  $v_Separator = "<hr style='border:3px solid #eee;' />"; 
  46.   
  47.  // used for data samples 
  48.  $v_CrmEndpoint = ($b_Sandbox) ? "https://sandbox.zohoapis." . $v_TLD . "/crm/v2" : "https://www.zohoapis." . $v_TLD . "/crm/v2"
  49.  $v_DataEndpoint = "https://inventory.zoho.com/api/v1/items?organization_id=012345678"// used to get sample data 
  50.   
  51.  // create temp folder if not exists 
  52.  if (!file_exists($v_TmpFolder)) { 
  53.      mkdir($v_TmpFolder, 0777, true)
  54.  } 
  55.  if (!file_exists($v_DelFolder)) { 
  56.      mkdir($v_DelFolder, 0777, true)
  57.  } 
  58.   
  59.  // get top level domain 
  60.  if(isset($_GET['location'])){ 
  61.      switch($_GET['location']){ 
  62.          case "us": 
  63.              $v_TLD = "com"
  64.              break
  65.          case "eu": 
  66.              $v_TLD = "eu"
  67.              break
  68.          default: 
  69.              $v_TLD = "com"
  70.      } 
  71.  }else{ 
  72.      $v_TLD = isset($_GET['tld']) ? strtolower($_GET['tld']) : "none"
  73.  } 
  74.   
  75.  // if client id in url then get and store 
  76.  if(isset($_GET['client_id'])){ 
  77.      if($_GET['client_id'] != ""){ 
  78.          file_put_contents($v_TmpFolder . "/" . $v_TmpFile, json_encode($_GET, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))
  79.      } 
  80.  } 
  81.   
  82.  // begin HTML output 
  83.  echo "<html><head><title>JoelLipman - API v2 Tokenizer (" . strtoupper($v_TLD) . ")</title><style>"
  84.  $v_AppStyle = "body{font-family:Arial,Verdana,Sans-serif;background-color:#ccc;}select,option,input.txt{padding:5px 10px;outline:0;border:1px solid#ccc;border-radius:5px;}input.btn{padding:10px 20px;outline:0;color:#fff;background:#10bc83;border-radius:2px;border:0;-webkit-box-shadow:2px 2px 2px 0px rgba(51,51,51,0.3);-moz-box-shadow:2px 2px 2px 0px rgba(51,51,51,0.3);box-shadow:2px 2px 2px 0px rgba(51,51,51,0.3);cursor:pointer;}input.txt{width:400px;}form{margin:20px;}h3{margin:0;}select{float:right;}td{padding:2px 5px;}label{display:inline-block;width:100px;}div.panel{background-color:#fff;border-radius:10px;margin:20px 10px;padding:20px 10px;-webkit-box-shadow:5px 5px 5px 0px rgba(51,51,51,0.3);-moz-box-shadow:5px 5px 5px 0px rgba(51,51,51,0.3);box-shadow:5px 5px 5px 0px rgba(51,51,51,0.3);}form{margin:0;padding:0;}#copyright{bottom:0;margin:0 auto;text-align:center;width:97%;margin-bottom:10px;}#copyright a{color:#999;text-decoration:none;font-size:10pt;line-height:20px;}#menu_bottom a{color:#f00;text-decoration:none;bottom:30;font-size:10pt;line-height:20px;}.centered{text-align:center;margin:0 auto;}h5{color:#999;font-weight:100;font-size:10pt;margin-bottom:5px;margin-top:10px;}tt.source{color:#fff;background-color:#000;}tt.target{background-color:yellow;}tt.bold{color:red;font-weight:700;}ul{margin:10px 0 0 85px;font-size:75%;color:#666;padding:0;}pre{margin:0;}"; 
  85.  $v_AppStyleFormatted = trim(&#112;reg_replace('/\s+/', ' ', $v_AppStyle))
  86.  $a_ReplaceFrom1 = array("px ", "0 ", " a")
  87.  $a_ReplaceTo1 = array("px?", "0?", "?a")
  88.  $v_AppStyleFormatted = str_replace($a_ReplaceFrom1, $a_ReplaceTo1, $v_AppStyleFormatted)
  89.  $a_ReplaceFrom2 = array(" ", "?")
  90.  $a_ReplaceTo2 = array("", " ")
  91.  $v_AppStyleFormatted = str_replace($a_ReplaceFrom2, $a_ReplaceTo2, $v_AppStyleFormatted)
  92.  echo $v_AppStyleFormatted . "</style></head><body>"
  93.  if((isset($_GET['tld']) || isset($_GET['location']))&&(file_exists($v_TmpFolder . "/" . $v_TmpFile))){ 
  94.      // 
  95.      // 0. initialize vars 
  96.      // 
  97.      echo "<div class='panel'>"
  98.      echo "<h3>0. INITIALIZE...</h3>"
  99.      echo "<h5>Info:</h5>"
  100.      $v_AuthEndpoint = "https://accounts.zoho." . $v_TLD . "/oauth/v2/auth"
  101.      $v_TokenEndpoint = "https://accounts.zoho." . $v_TLD . "/oauth/v2/token"
  102.      if(file_exists($v_TmpFolder . "/" . $v_TmpFile) && isset($_GET['code'])){ 
  103.          $m_GrantInfo = json_decode(file_get_contents($v_TmpFolder . "/" . $v_TmpFile), true)
  104.          $a_CheckFields['client_id'] = $m_GrantInfo['client_id']
  105.          $a_CheckFields['client_secret'] = $m_GrantInfo['client_secret']
  106.          $a_CheckFields['scopes'] = $m_GrantInfo['scopes']
  107.          $a_CheckFields['tld'] = $m_GrantInfo['tld']
  108.          $a_CheckFields['redirect_uri'] = $m_GrantInfo['redirect_uri']
  109.          rename($v_TmpFolder . "/" . $v_TmpFile, $v_DelFolder . "/" . $v_TmpFile)
  110.      }else{ 
  111.          $a_CheckFields['client_id'] = $_GET['client_id']
  112.          $a_CheckFields['client_secret'] = $_GET['client_secret']
  113.          $a_CheckFields['scopes'] = $_GET['scopes']
  114.          $a_CheckFields['tld'] = $_GET['tld']
  115.          $a_CheckFields['redirect_uri'] = $_GET['redirect_uri']
  116.      } 
  117.      $a_InitFields['tld'] = $a_CheckFields['tld']
  118.      $a_InitFields['client_id'] = $a_CheckFields['client_id']
  119.      $a_InitFields['client_secret'] = $a_CheckFields['client_secret']
  120.      $a_InitFields['scopes'] = $a_CheckFields['scopes']
  121.      $a_InitFields['redirect_uri'] = $a_CheckFields['redirect_uri']
  122.      $a_Info = array()
  123.      $a_Info['api']['endpoint'] = $v_AuthEndpoint
  124.      $a_Info['api']['header'] = false
  125.      $a_Info['api']['ssl_check'] = false
  126.      $a_Info['sandbox'] = $b_Sandbox ? "<tt class='bold'>true</tt>" : "<tt class='bold'>false</tt>"
  127.      echo "<pre>" . json_encode(array_merge($a_InitFields,$a_Info), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  128.      // 
  129.      // 1. get grant token 
  130.      // 
  131.      echo "</div><div class='panel'>"
  132.      echo "<h3>1. GRANT TOKEN...</h3>"
  133.      if($a_CheckFields['client_id']!="") 
  134.      { 
  135.          $v_GrantTokenPath = $v_RootFolder . $a_CheckFields['client_id'] . "/grant_token.dat"
  136.          $v_RefreshTokenPath = $v_RootFolder . $a_CheckFields['client_id'] . "/refresh_token.dat"
  137.          $v_AccessTokenPath = $v_RootFolder . $a_CheckFields['client_id'] . "/access_token.dat"
  138.          $v_WorkingFolder = $v_RootFolder . $a_CheckFields['client_id']
  139.          if (!file_exists($v_WorkingFolder)) { 
  140.              mkdir($v_WorkingFolder, 0777, true)
  141.          } 
  142.      } 
  143.      echo "<h5>Info:</h5>"
  144.      $a_Info = array()
  145.      $a_Info['api']['endpoint'] = $v_AuthEndpoint
  146.      $a_Info['api']['method'] = "GET"
  147.      $a_Info['api']['header'] = false
  148.      $a_Info['api']['ssl_check'] = false
  149.      $a_Info['expires'] = "<tt class='bold'>" . "in 1 to 10 minutes" . "</tt>"
  150.      $a_Info['date_created'] = file_exists($v_GrantTokenPath) ? date("Y-m-d H:i:s P", filectime($v_GrantTokenPath)) : date("Y-m-d H:i:s P")
  151.      $a_Info['date_modified'] = file_exists($v_GrantTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_GrantTokenPath)) : date("Y-m-d H:i:s P")
  152.      $a_Info['date_expires'] = file_exists($v_GrantTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_GrantTokenPath) + 300) : date("Y-m-d H:i:s P", strtotime(time() + 300))
  153.      echo "<pre>" . json_encode($a_Info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  154.      echo "<h5>Request (GET):</h5>"
  155.      $a_Request = array()
  156.      $a_Request['client_id'] = $a_CheckFields['client_id']
  157.      $a_Request['redirect_uri'] = $a_CheckFields['redirect_uri']
  158.      $a_Request['scope'] = $a_CheckFields['scopes']
  159.      $a_Request['response_type'] = "code"
  160.      $a_Request['access_type'] = "offline"
  161.      $a_Request['prompt'] = "consent"
  162.      $a_Request['state'] = "testing"
  163.      echo "<pre>" . json_encode($a_Request, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  164.      $v_GrantTokenUrl = $v_AuthEndpoint . "?" . http_build_query($a_Request)
  165.      if(isset($_GET['code'])) 
  166.      { 
  167.          echo "<h5>Response (GET):</h5>"
  168.          $a_Response = array()
  169.          $a_Response['state'] = $_GET['state']
  170.          $a_Response['code'] = $_GET['code']
  171.          $a_Response['location'] = $_GET['location']
  172.          $a_Response['accounts-server'] = $_GET['accounts-server']
  173.          if($v_GrantTokenPath != "" && $b_Sandbox){ 
  174.              file_put_contents($v_GrantTokenPath, json_encode($a_Response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))
  175.          } 
  176.          foreach($a_Response as $v_Key => $v_Value){ 
  177.              if($v_Key == "code"){ 
  178.                  $a_Response['code'] = "<tt class='source'>" . $v_Value . "</tt>"
  179.              } 
  180.          } 
  181.          echo "<pre>" . json_encode($a_Response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  182.          // 
  183.          // 2. get refresh token 
  184.          // 
  185.          echo "</div><div class='panel'>"
  186.          echo "<h3>2. REFRESH TOKEN...</h3>"
  187.          echo "<h5>Info:</h5>"
  188.          $a_Info = array()
  189.          $a_Info['api']['endpoint'] = $v_TokenEndpoint
  190.          $a_Info['api']['method'] = "POST"
  191.          $a_Info['api']['header'] = false
  192.          $a_Info['api']['ssl_check'] = false
  193.          $a_Info['expires'] = "<tt class='bold'>" . "Unless revoked or overwritten, a refresh token NEVER expires." . "</tt>"
  194.          $a_Info['date_created'] = file_exists($v_RefreshTokenPath) ? date("Y-m-d H:i:s P", filectime($v_RefreshTokenPath)) : date("Y-m-d H:i:s P")
  195.          $a_Info['date_modified'] = file_exists($v_RefreshTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_RefreshTokenPath)) : date("Y-m-d H:i:s P")
  196.          echo "<pre>" . json_encode($a_Info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  197.          echo "<h5>Request (POST):</h5>"
  198.          $a_Payload = array()
  199.          $a_Payload['code'] = $_GET['code']
  200.          $a_Payload['client_id'] = $a_CheckFields['client_id']
  201.          $a_Payload['client_secret'] = $a_CheckFields['client_secret']
  202.          $a_Payload['redirect_uri'] = $a_CheckFields['redirect_uri']
  203.          $a_Payload['grant_type'] = "authorization_code"
  204.          $h_Curl=curl_init()
  205.          $a_CurlOptions = array( 
  206.              CURLOPT_URL => $v_TokenEndpoint, 
  207.              CURLOPT_CUSTOMREQUEST => "POST", 
  208.              CURLOPT_POSTFIELDS => $a_Payload, 
  209.              CURLOPT_HEADER => 0, 
  210.              CURLOPT_VERBOSE => 0, 
  211.              CURLOPT_RETURNTRANSFER => 1, 
  212.              CURLOPT_SSL_VERIFYPEER => 0, 
  213.              CURLOPT_SSL_VERIFYHOST => 0 
  214.          )
  215.          curl_setopt_array($h_Curl, $a_CurlOptions)
  216.          $v_RefreshCurl = curl_exec($h_Curl)
  217.          curl_close($h_Curl)
  218.          foreach($a_Payload as $v_Key => $v_Value){ 
  219.              if($v_Key == "code"){ 
  220.                  $a_Payload['code'] = "<tt class='target'>" . $v_Value . "</tt>"
  221.              } 
  222.          } 
  223.          echo "<pre>" . json_encode($a_Payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  224.          echo "<h5>Response (POST):</h5>"
  225.          $a_RefreshResponse = json_decode($v_RefreshCurl, true)
  226.          $v_RefreshToken = "ERROR"
  227.          if($v_RefreshTokenPath != ""){ 
  228.              file_put_contents($v_RefreshTokenPath, json_encode($a_RefreshResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))
  229.          } 
  230.          if(isset($a_RefreshResponse['refresh_token'])) 
  231.          { 
  232.              $v_RefreshToken = $a_RefreshResponse['refresh_token']
  233.              foreach($a_RefreshResponse as $v_Key => $v_Value){ 
  234.                  if($v_Key == "refresh_token"){ 
  235.                      $a_RefreshResponse['refresh_token'] = "<tt class='source'>" . $v_Value . "</tt>"
  236.                  } 
  237.              } 
  238.              echo "<pre>" . json_encode($a_RefreshResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  239.          }else{ 
  240.              echo "<pre>ERROR: Check your submitted variables are the same: Client ID, Client Secret, Redirect URI, Scope(s)</pre>"
  241.          } 
  242.          // 
  243.          // 3. get access token 
  244.          // 
  245.          echo "</div><div class='panel'>"
  246.          echo "<h3>3. ACCESS TOKEN...</h3>"
  247.          echo "<h5>Info:</h5>"
  248.          $a_Info = array()
  249.          $a_Info['api']['endpoint'] = $v_TokenEndpoint
  250.          $a_Info['api']['method'] = "POST"
  251.          $a_Info['api']['header'] = false
  252.          $a_Info['api']['ssl_check'] = false
  253.          $a_Info['expires'] = "<tt class='bold'>" . "in 1 hour after creation" . "</tt>"
  254.          $a_Info['date_created'] = file_exists($v_AccessTokenPath) ? date("Y-m-d H:i:s P", filectime($v_AccessTokenPath)) : date("Y-m-d H:i:s P")
  255.          $a_Info['date_modified'] = file_exists($v_AccessTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_AccessTokenPath)) : date("Y-m-d H:i:s P")
  256.          $a_Info['date_expires'] = file_exists($v_AccessTokenPath) ? date("Y-m-d H:i:s P", filemtime($v_AccessTokenPath) + 3600) : date("Y-m-d H:i:s P", strtotime(time() + 3600))
  257.          echo "<pre>" . json_encode($a_Info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  258.          echo "<h5>Request (POST):</h5>"
  259.          $a_Payload = array()
  260.          $a_Payload['refresh_token'] = $v_RefreshToken
  261.          $a_Payload['client_id'] = $a_CheckFields['client_id']
  262.          $a_Payload['client_secret'] = $a_CheckFields['client_secret']
  263.          $a_Payload['redirect_uri'] = $a_CheckFields['redirect_uri']
  264.          $a_Payload['grant_type'] = "refresh_token"
  265.          $h_Curl=curl_init()
  266.          $a_CurlOptions = array( 
  267.              CURLOPT_URL => $v_TokenEndpoint, 
  268.              CURLOPT_CUSTOMREQUEST => "POST", 
  269.              CURLOPT_POSTFIELDS => $a_Payload, 
  270.              CURLOPT_HEADER => 0, 
  271.              CURLOPT_VERBOSE => 0, 
  272.              CURLOPT_RETURNTRANSFER => 1, 
  273.              CURLOPT_SSL_VERIFYPEER => 0, 
  274.              CURLOPT_SSL_VERIFYHOST => 0 
  275.          )
  276.          curl_setopt_array($h_Curl, $a_CurlOptions)
  277.          $v_AccessCurl = curl_exec($h_Curl)
  278.          curl_close($h_Curl)
  279.          $a_AccessResponse = json_decode($v_AccessCurl, true)
  280.          if($v_AccessTokenPath != ""){ 
  281.              file_put_contents($v_AccessTokenPath, json_encode($a_AccessResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))
  282.          } 
  283.          foreach($a_Payload as $v_Key => $v_Value){ 
  284.              if($v_Key == "refresh_token"){ 
  285.                  $a_Payload['refresh_token'] = "<tt class='target'>" . $v_Value . "</tt>"
  286.              } 
  287.          } 
  288.          echo "<pre>" . json_encode($a_Payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  289.          echo "<h5>Response (POST):</h5>"
  290.          if(isset($a_AccessResponse['access_token'])){ 
  291.              $v_AccessToken = $a_AccessResponse['access_token']
  292.              foreach($a_AccessResponse as $v_Key => $v_Value){ 
  293.                  if($v_Key == "access_token"){ 
  294.                      $a_AccessResponse['access_token'] = "<tt class='source'>" . $v_Value . "</tt>"
  295.                  } 
  296.              } 
  297.              echo "<pre>" . json_encode($a_AccessResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  298.          }else{ 
  299.              echo "<pre>ERROR: Check the Refresh Token</pre>"
  300.          } 
  301.          // 
  302.          // 4. get data sample (last 2 records) 
  303.          // 
  304.          echo "</div><div class='panel'>"
  305.          echo "<h3>4. DATA SAMPLE...</h3>"
  306.          echo "<h5>Info:</h5>"
  307.          $a_Info = array()
  308.          $a_Info['api']['endpoint'] = $v_DataEndpoint
  309.          $a_Info['api']['method'] = "GET"
  310.          $a_Info['api']['header'] = true
  311.          $a_Info['api']['ssl_check'] = false
  312.          $a_Info['date'] = date("Y-m-d H:i:s P")
  313.          echo "<pre>" . json_encode($a_Info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  314.          echo "<h5>Request (GET):</h5>"
  315.          $a_Header = array()
  316.          $a_Header[] = "Authorization: Zoho-oauthtoken " . $v_AccessToken
  317.          $a_Header[] = "Content-Type: application/x-www-form-urlencoded;charset=UTF-8"
  318.          $a_Payload = array()
  319.          $a_Payload['page'] = 1
  320.          $a_Payload['per_page'] = 2
  321.          $a_Payload['sort_by'] = "id"
  322.          $a_Payload['sort_order'] = "D";  // in CRM this is "asc" or "desc" in books/inventory this is either "A" or "D" 
  323.          $a_Request = array("header" => $a_Header, "parameters" => $a_Payload)
  324.          $h_Curl=curl_init()
  325.          $a_CurlOptions = array( 
  326.              CURLOPT_URL => $v_DataEndpoint . "&" . http_build_query($a_Payload), 
  327.              CURLOPT_CUSTOMREQUEST => "GET", 
  328.              CURLOPT_HEADER => 1, 
  329.              CURLOPT_HTTPHEADER => $a_Header, 
  330.              CURLOPT_VERBOSE => 0, 
  331.              CURLOPT_RETURNTRANSFER => 1, 
  332.              CURLOPT_SSL_VERIFYPEER => 0, 
  333.              CURLOPT_SSL_VERIFYHOST => 0 
  334.          )
  335.          curl_setopt_array($h_Curl, $a_CurlOptions)
  336.          $v_DataCurl = curl_exec($h_Curl)
  337.          foreach($a_Request as $v_Key => $v_Value){ 
  338.              if($v_Key == 0){ 
  339.                  $a_Request['header'][0] = str_replace("Zoho-oauthtoken ","Zoho-oauthtoken <tt class='target'>", $a_Request['header'][0]) . "</tt>"
  340.              } 
  341.          } 
  342.          echo "<pre>" . json_encode($a_Request, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  343.          echo "<h5>Response (GET):</h5>"
  344.          if(stripos($v_DataCurl, '"code"')>0){ 
  345.              $v_DataStart = stripos($v_DataCurl, '{"code"')
  346.              $v_DataStr = trim(substr($v_DataCurl, $v_DataStart))
  347.              $a_DataResponse = json_decode($v_DataStr, true)
  348.              $a_WriteJson['response'] = $a_DataResponse
  349.              echo "<pre>" . json_encode($a_WriteJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  350.          }elseif(stripos($v_DataCurl, '"data"')>0){ 
  351.              $v_DataStart = stripos($v_DataCurl, '{"data"')
  352.              $v_DataStr = trim(substr($v_DataCurl, $v_DataStart))
  353.              $a_DataResponse = json_decode($v_DataStr, true)
  354.              $v_Index = 0
  355.              foreach($a_DataResponse['data'] as $a_Contact){ 
  356.                  $a_WriteJson['records'][$v_Index]['id'] = $a_Contact['id']
  357.                  $a_WriteJson['records'][$v_Index]['name'] = $a_Contact['Full_Name']
  358.                  $a_WriteJson['records'][$v_Index]['email'] = $a_Contact['Email']
  359.                  $a_WriteJson['records'][$v_Index]['created_time'] = str_replace("+", +", str_replace("T", " ", $a_Contact['Created_Time']))
  360.                  $v_Index++
  361.              } 
  362.              echo "<pre>" . json_encode($a_WriteJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "</pre>"
  363.          }else{ 
  364.              echo "<pre>" . $v_DataCurl . "</pre>"
  365.          } 
  366.          echo "</div>"
  367.      } 
  368.      else 
  369.      { 
  370.          echo "<br /><form>"
  371.          echo '<input type="button" value="Click here to open App Permissions &#187;" onclick="javascript:top.location.href=\'' . $v_GrantTokenUrl . '\';" class="btn" style="margin-left: 70px;" />'; 
  372.          echo "</form> 
  373.          <ul> 
  374.          <li>Login as a Zoho User with access to the API</li> 
  375.          <li>Read and click on accept to allow the app access.</li> 
  376.          <li>There is a limit of 5 refresh tokens per minute.</li> 
  377.          <li>There is a limit of 19 refresh tokens per app.</li> 
  378.          <li>The 20th refresh token will overwrite the 1st.</li> 
  379.          </ul><br />"
  380.      } 
  381.   
  382.      // END 
  383.      echo "</div><div class='panel'>"
  384.      echo "<h5>Script finished.</h5>"
  385.      echo "<div id='menu_bottom' style='position:normal'><a href='./get_tokens.php' target='_top'>&#187; Start Over</a></div></div>"; 
  386.      echo "<div id='copyright' style='position:normal'><a href='//joellipman.com/' target='_top'>Copyright © ".date("Y")." Joel Lipman Ltd</a></div></div>"
  387.  }else{ 
  388.      $v_TldSelected = ""
  389.      $a_TldOptions = array('com','com.au','com.cn','eu','in')
  390.      echo " 
  391.  <div class='panel'><form method='get' action='get_tokens.php'> 
  392.      <h3>Configure App</h3> 
  393.      <table> 
  394.      <tr><td colspan='2'>Please select the domain your ZohoCRM is on:<br /><i style='font-size:75%;'>(eg. https://crm.zoho.com = COM, https://crm.zoho.eu = EU)</i>   <select name='tld' id='tldSelect'>"
  395.      foreach($a_TldOptions as $v_Option){ 
  396.          $v_SelectedStr = (strtolower($v_TLD) == $v_Option) ? " selected='true'" : ""
  397.          echo "<option value='" . strtolower($v_Option) . "'" . $v_SelectedStr . ">" . strtoupper($v_Option) . "</option>"
  398.      } 
  399.      echo " 
  400.      </select></td></tr> 
  401.      <tr><td colspan='2' class='centered'><input type='button' value='Open Zoho App Registration' onclick='javascript:openAppConsole();' class='btn' /></td></tr> 
  402.      <tr><td colspan='2'> </td></tr> 
  403.      <tr><td>Client ID</td><td><input name='client_id' value='' class='txt' onclick='this.select();' /></td></tr> 
  404.      <tr><td>Client Secret</td><td><input name='client_secret' value='' class='txt' onclick='this.select();' /></td></tr> 
  405.      <tr><td>Scopes</td><td><input name='scopes' value='ZohoCRM.modules.ALL,ZohoCRM.settings.READ,ZohoCRM.users.READ' class='txt' onclick='this.select();' /></td></tr> 
  406.      <tr><td>Redirect URI</td><td><input name='redirect_uri' value='" . $v_RedirectUri . "' class='txt' onclick='this.select();' /></td></tr> 
  407.      <tr><td colspan='2' style='text-align:center;'><input type='submit' value='Get Grant Token' class='btn' /></td></tr> 
  408.      </table> 
  409.  </form></div> 
  410.  <script> 
  411.  function openAppConsole() { 
  412.      var e = document.getElementById('tldSelect')
  413.      var strOption = e.options[e.selectedIndex].value; 
  414.      window.open('https://accounts.zoho.' + strOption + '/developerconsole', '_blank', 'location=yes,height=600,width=800,scrollbars=yes,status=yes')
  415.  } 
  416.  </script> 
  417.  "
  418.      echo "<div id='copyright' style='position:fixed'><a href='//joellipman.com/' target='_top'>Copyright © ".date("Y")." Joel Lipman Ltd</a></div>"
  419.  } 
  420.  echo "</body></html>"

Warning(s):
  • Do not have multiple staff using this file at the same time.
  • Make a note of the refresh_token as this is all you need to access data (use it to generate an access_token).
    • The refresh token does NOT expire unless overwritten or revoked.
    • There is a limit of 5 refresh tokens per minute.
    • There is a limit of 19 refresh tokens per app.
    • The 20th refresh token will overwrite the 1st.
  • An access token will last 1 hour.

Minimal Upsert
I'm including the code below in this article because it is possibly clearer to understand (and sometimes I need a quick reference):
copyraw
<?php
// init
$v_DataEndpoint = "https://sandbox.zohoapis.com/crm/v2/CustomModuleApiName/upsert";
$v_AccessToken = "<ENTER_YOUR_ACCESS_TOKEN_VALUE_HERE>";
$a_Header = array('Authorization: Zoho-oauthtoken '.$v_AccessToken, 'Content-Type: application/json');

// set record mandatory field(s) value
$a_Data['data'] = array();
$a_Data['data'][0]['Name'] = "Upsert Test via PHP/cURL Script";

// setup cURL request
$h_cURL=curl_init();
curl_setopt($h_cURL, CURLOPT_HEADER, 0);
curl_setopt($h_cURL, CURLOPT_HTTPHEADER, $a_Header);
curl_setopt($h_cURL, CURLOPT_VERBOSE, 0);
curl_setopt($h_cURL, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($h_cURL, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($h_cURL, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($h_cURL, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($h_cURL, CURLOPT_POSTFIELDS, json_encode($a_Data));
curl_setopt($h_cURL, CURLOPT_URL, $v_DataEndpoint);
$r_cURL = curl_exec($h_cURL);
if (curl_exec($h_cURL) === false) {
	$v_Output = curl_error($h_cURL);
} else {
	$v_Output = $r_cURL;
}
curl_close($h_cURL);

// output
echo $v_Output;
  1.  <?php 
  2.  // init 
  3.  $v_DataEndpoint = "https://sandbox.zohoapis.com/crm/v2/CustomModuleApiName/upsert"
  4.  $v_AccessToken = "<ENTER_YOUR_ACCESS_TOKEN_VALUE_HERE>"
  5.  $a_Header = array('Authorization: Zoho-oauthtoken '.$v_AccessToken, 'Content-Type: application/json')
  6.   
  7.  // set record mandatory field(s) value 
  8.  $a_Data['data'] = array()
  9.  $a_Data['data'][0]['Name'] = "Upsert Test via PHP/cURL Script"
  10.   
  11.  // setup cURL request 
  12.  $h_cURL=curl_init()
  13.  curl_setopt($h_cURL, CURLOPT_HEADER, 0)
  14.  curl_setopt($h_cURL, CURLOPT_HTTPHEADER, $a_Header)
  15.  curl_setopt($h_cURL, CURLOPT_VERBOSE, 0)
  16.  curl_setopt($h_cURL, CURLOPT_RETURNTRANSFER, 1)
  17.  curl_setopt($h_cURL, CURLOPT_SSL_VERIFYPEER, 0)
  18.  curl_setopt($h_cURL, CURLOPT_SSL_VERIFYHOST, 0)
  19.  curl_setopt($h_cURL, CURLOPT_CUSTOMREQUEST, 'POST')
  20.  curl_setopt($h_cURL, CURLOPT_POSTFIELDS, json_encode($a_Data))
  21.  curl_setopt($h_cURL, CURLOPT_URL, $v_DataEndpoint)
  22.  $r_cURL = curl_exec($h_cURL)
  23.  if (curl_exec($h_cURL) === false) { 
  24.      $v_Output = curl_error($h_cURL)
  25.  } else { 
  26.      $v_Output = $r_cURL
  27.  } 
  28.  curl_close($h_cURL)
  29.   
  30.  // output 
  31.  echo $v_Output
Category: Zoho :: Article: 664

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

Related Articles

Joes Revolver Map

Joes Word Cloud

Accreditation

Badge - Certified Zoho Creator Associate
Badge - Certified Zoho Creator Associate

Donate & Support

If you like my content, and would like to support this sharing site, feel free to donate using a method below:

Paypal:
Donate to Joel Lipman via PayPal

Bitcoin:
Donate to Joel Lipman with Bitcoin bc1qf6elrdxc968h0k673l2djc9wrpazhqtxw8qqp4

Ethereum:
Donate to Joel Lipman with Ethereum 0xb038962F3809b425D661EF5D22294Cf45E02FebF
© 2024 Joel Lipman .com. All Rights Reserved.