This is an article documenting the process of publishing a Zoho Inventory image to eBay's Picture Hosting Services (EPS).
Why?
Because I couldn't find any other article in the whole world wide web that had a working solution. My use-case here is that I have built an eBay integration between Zoho Inventory and... eBay. Out-of-the-box, and at-time-of-print, Zoho Inventory only syncs eBay one way, in other words, listings on eBay get downloaded to Zoho Inventory but not vice-versa. Then there's an added delay of 4 hours. Then item specifics in eBay's listings don't get parsed into custom fields in Zoho Inventory... and the list goes on. So in my job, we tend to create a few custom functions which will download any item from eBay to Zoho Inventory and vice-versa. Easy to make the function to push an item to an eBay listing (see my other articles for this). But we also want to push an image from Zoho Inventory to eBay.
How?
Again, there is a caveat in that the subscription you have or your client has, needs to be Zoho One. Because we're going to use Zoho Creator as a middleware. Just to add to the pain, I'm going to make my function in Zoho CRM:
Overview
- Create a REST API CRM function that can return the ebay URL of the image.
- Done
High Level Overview
- Create a REST API CRM function
- Get Zoho CRM to create a Zoho Creator record by passing it the Inventory ID
- Get Zoho Creator to accept this ID, download the file, and upload it to a "File Upload" field
- Get Zoho Creator to generate a published / public URL of the file
- Get Zoho CRM to scan Creator for the public URL and retrieve this
- Get Zoho CRM to generate an XML request to upload the photo to eBay's Hosted Picture Services
- Get Zoho CRM to parse the eBay response and return the eBay hosted URL
Detailed Step-by-step
- Add Zoho Creator to your Zoho One
- Login to Zoho One
- Go to Settings (cog in top right)
- Click on Applications i the sidebar
- Click on the Add Application button
- Select Creator by clicking the Add button below it
- Link it if applicable to your super admin account
- Create an app, a form and publish it in Zoho Creator
- Create an app by clicking on Create App
- then go into the app and Create a form, I'm calling mine Inventory Photo
- Add 3 fields to this form
- Zoho Inventory ID which is a single-line/text field
- Creator File which is a file upload field
- Public URL which is a URL field
- Publish it by going to Settings > Publish > Publish Component > and select the REPORT (eg. "Inventory Photo Report"). Note the embed code, specifically the long alphanumeric string.
- Create a connection from Zoho Creator to Zoho Inventory
- 3 lines icon in the top-left of your Creator (in edit this application mode)
- Then go to Setup > Connections > Add Connection
- Select Zoho OAuth and specify the scope(s) ZohoInventory.items.READ (add some more if you're unsure...)
- Give it a connection name and note down the connection link name. I'm calling mine joel_zoho
- Authorize it, etc.
- Make a workflow which does the clever bit in Zoho Creator:
- Go to Workflow > Form Workflows > New Workflow
- Choose the form > Run when Created or Edited > Trigger on Successful form submission > and name it OnSubmit > click on Create Workflow
- Select you want to run some Deluge Script and plug in the following codecopyraw
if(!isnull(input.Zoho_Inventory_ID)) { // // specify your own Inventory/Books organization ID v_BooksOrgID = 20221234567; // // determine URL of file to download (note the DC here is EU but yours might be COM) v_Url = "https://inventory.zoho.eu/api/v1/items/" + input.Zoho_Inventory_ID + "/image?organization_id=" + v_BooksOrgID + ""; r_File = invokeurl [ url :v_Url type :GET connection:"joel_zoho" ]; r_File.setParamName("file"); // // store this file in the field "Creator File" input.Creator_File = r_File; // // use publish key of the report v_PublishKey = "AAAAABBBBBCCCCDDDDEEEFFFGGGGHHHIIIJJJKKLLLMMMNNOOOPPPQQWRRRSSTTUUVVWWXXYYZZ122345567890"; // // determine the public URL of the file on this record v_PublicUrl = "https://creatorapp.zohopublic.eu/file" + zoho.appuri + "Inventory_Photo_Report/" + input.ID + "/Creator_File/download/" + v_PublishKey + "?filepath=/" + input.Creator_File; // // add it to the public URL field input.Public_URL = v_PublicUrl; }
- if(!isnull(input.Zoho_Inventory_ID))
- {
- //
- // specify your own Inventory/Books organization ID
- v_BooksOrgID = 20221234567;
- //
- // determine URL of file to download (note the DC here is EU but yours might be COM)
- v_Url = "https://inventory.zoho.eu/api/v1/items/" + input.Zoho_Inventory_ID + "/image?organization_id=" + v_BooksOrgID + "";
- r_File = invokeUrl
- [
- url :v_Url
- type :GET
- connection:"joel_zoho"
- ];
- r_File.setParamName("file");
- //
- // store this file in the field "Creator File"
- input.Creator_File = r_File;
- //
- // use publish key of the report
- v_PublishKey = "AAAAABBBBBCCCCDDDDEEEFFFGGGGHHHIIIJJJKKLLLMMMNNOOOPPPQQWRRRSSTTUUVVWWXXYYZZ122345567890";
- //
- // determine the public URL of the file on this record
- v_PublicUrl = "https://creatorapp.zohopublic.eu/file" + zoho.appuri + "Inventory_Photo_Report/" + input.ID + "/Creator_File/download/" + v_PublishKey + "?filepath=/" + input.Creator_File;
- //
- // add it to the public URL field
- input.Public_URL = v_PublicUrl;
- }
- Create a workflow to tidy up the records daily in Zoho Creator
- Go to Workflow > Schedules > New Workflow
- Set the Start date and time to tomorrow's date at 2:00am
- Run Daily
- Name the workflow Daily Delete Record
- Click on Create Workflow
- Give it the following code:copyraw
for each r_ApplicableRecord in Inventory_Photo[Added_Time <= zoho.currenttime.subDay(1)] { delete from Inventory_Photo[ID == r_ApplicableRecord.ID]; }
- for each r_ApplicableRecord in Inventory_Photo[Added_Time <= zoho.currenttime.subDay(1)]
- {
- delete from Inventory_Photo[ID == r_ApplicableRecord.ID];
- }
- Create a CRM connection to Zoho Creator
- Login to ZohoCRM
- Go to Setup > Developer Space > Connections
- Create Connection
- Give it the scope(s): ZohoCreator.form.CREATE and ZohoCreator.report.READ (more if this doesn't work properly)
- Give it a name, I'm calling mine joel_creator
- Authorize it, etc.
- Create a REST API CRM Function
- Login to ZohoCRM
- Go to Setup > Functions > New Function
- Give it a name and such, I'm doing the following
- Function Name: fn_eBay_UploadPhoto
- Display Name: Fn - eBay - Upload Photo
- Description: What it says on the tin
- Category: Standalone
- Click on Create
- Give it the parameter/argument p_InventoryitemID as an Integer/Long/Number datatype
- As the first part of your code, enter the following as code (you'll need to speify your own App Owner and App Name):
copyraw
// // ************************************************** CREATOR ************************************************** v_ItemId = ifnull(p_InventoryItemID,0); v_PublicUrl = ""; // // send Zoho Creator the inventory ID v_AppOwner = "JoelGoHappy"; v_AppName = "ZohoInventory_eBayPictureServices"; v_FormName = "Inventory_Photo"; v_ReportName = "Inventory_Photo_Report"; m_Blank = Map(); m_CreatorParams = Map(); m_CreatorParams.put("Zoho_Inventory_ID",v_ItemId); r_Creator = zoho.creator.createRecord(v_AppOwner,v_AppName,v_FormName,m_CreatorParams,m_Blank,"joel_creator"); if(!isnull(r_Creator.get("data"))) { if(!isnull(r_Creator.get("data").get("ID"))) { r_CreatorDetails = zoho.creator.getRecordById(v_AppOwner,v_AppName,v_ReportName,r_Creator.get("data").get("ID").toLong(),"joel_creator"); if(!isnull(r_CreatorDetails.get("data"))) { if(!isnull(r_CreatorDetails.get("data").get("Public_URL").get("url"))) { v_PublicUrl = r_CreatorDetails.get("data").get("Public_URL").get("url"); } } } } info r_Creator;
- //
- // ************************************************** CREATOR **************************************************
- v_ItemId = ifnull(p_InventoryItemID,0);
- v_PublicUrl = "";
- //
- // send Zoho Creator the inventory ID
- v_AppOwner = "JoelGoHappy";
- v_AppName = "ZohoInventory_eBayPictureServices";
- v_FormName = "Inventory_Photo";
- v_ReportName = "Inventory_Photo_Report";
- m_Blank = Map();
- m_CreatorParams = Map();
- m_CreatorParams.put("Zoho_Inventory_ID",v_ItemId);
- r_Creator = zoho.creator.createRecord(v_AppOwner,v_AppName,v_FormName,m_CreatorParams,m_Blank,"joel_creator");
- if(!isnull(r_Creator.get("data")))
- {
- if(!isnull(r_Creator.get("data").get("ID")))
- {
- r_CreatorDetails = zoho.creator.getRecordById(v_AppOwner,v_AppName,v_ReportName,r_Creator.get("data").get("ID").toLong(),"joel_creator");
- if(!isnull(r_CreatorDetails.get("data")))
- {
- if(!isnull(r_CreatorDetails.get("data").get("Public_URL").get("url")))
- {
- v_PublicUrl = r_CreatorDetails.get("data").get("Public_URL").get("url");
- }
- }
- }
- }
- info r_Creator;
- And here's the 2nd part of the function in CRMcopyraw
// // ************************************************** EBAY ************************************************** // init v_EbayImageURL = ""; // // eval v_Endpoint = "https://api.ebay.com/ws/api.dll"; v_ApiCall = "UploadSiteHostedPictures"; // // a CRM record that holds my API connection details v_IntegrationRecordID = 123456789012345678; r_CrmRecord = zoho.crm.getRecordById("Integrations",v_IntegrationRecordID); v_TradingAPIVersion = r_CrmRecord.get("Compatibility_Version"); // // Another REST API CRM function which generates an eBay Access Token for me r_Response = getUrl("https://www.zohoapis.eu/crm/v2/functions/fn_ebay_getaccesstoken/actions/execute?auth_type=apikey&zapikey=1003.<REST_OF_YOUR_ZAPIKEY>"); if(!isnull(r_Response.toMap().get("details"))) { if(!isnull(r_Response.toMap().get("details").get("output"))) { v_AccessToken = r_Response.toMap().get("details").get("output"); } } info v_AccessToken; // // build header m_Headers = Map(); m_Headers.put("X-EBAY-API-SITEID",3); m_Headers.put("X-EBAY-API-COMPATIBILITY-LEVEL",v_TradingAPIVersion); m_Headers.put("X-EBAY-API-CALL-NAME",v_ApiCall); m_Headers.put("X-EBAY-API-IAF-TOKEN",v_AccessToken); // // build params m_Params = Map(); m_Params.put("WarningLevel","High"); m_Params.put("ErrorLanguage","en_GB"); m_Params.put("ExternalPictureURL",v_PublicUrl); m_Params.put("PictureName","OhBeautifulMe"); m_Params.put("PictureSet","Supersize"); // // convert to xml and replace root nodes x_Params = m_Params.toXML(); x_Params = x_Params.toString().replaceFirst("<root>","<?xml version=\"1.0\" encoding=\"utf-8\"?><" + v_ApiCall + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">"); x_Params = x_Params.toString().replaceFirst("</root>","</" + v_ApiCall + "Request>"); info x_Params; // // send the request XML as a string r_ResponseXML = invokeurl [ url :v_Endpoint type :POST parameters:x_Params headers:m_Headers ]; info r_ResponseXML; // // parse response and retrieve the ebay URL of the hosted image if(r_ResponseXML.containsIgnoreCase("<Ack>Success</Ack>") || r_ResponseXML.containsIgnoreCase("<Ack>Warning</Ack>")) { v_BiggestHeight = 0; x_RespondedXML = r_ResponseXML.replaceFirst("xmlns=\"urn:ebay:apis:eBLBaseComponents\"","",true); x_PictureSets = x_RespondedXML.executeXPath("/"+v_ApiCall+"Response/SiteHostedPictureDetails/*").toXmlList(); for each r_PictureSet in x_PictureSets { if(r_PictureSet.contains("PictureSetMember")) { // get image that is the biggest/tallest v_ThisHeight = r_PictureSet.executeXPath("/PictureSetMember/PictureHeight/text()").toLong(); if(v_ThisHeight > v_BiggestHeight) { v_BiggestHeight = v_ThisHeight; v_EbayImageURL = r_PictureSet.executeXPath("/PictureSetMember/MemberURL/text()"); } } } } return v_EbayImageURL;
- //
- // ************************************************** EBAY **************************************************
- // init
- v_EbayImageURL = "";
- //
- // eval
- v_Endpoint = "https://api.ebay.com/ws/api.dll";
- v_ApiCall = "UploadSiteHostedPictures";
- //
- // a CRM record that holds my API connection details
- v_IntegrationRecordID = 123456789012345678;
- r_CrmRecord = zoho.crm.getRecordById("Integrations",v_IntegrationRecordID);
- v_TradingAPIVersion = r_CrmRecord.get("Compatibility_Version");
- //
- // Another REST API CRM function which generates an eBay Access Token for me
- r_Response = getUrl("https://www.zohoapis.eu/crm/v2/functions/fn_ebay_getaccesstoken/actions/execute?auth_type=apikey&zapikey=1003.<REST_OF_YOUR_ZAPIKEY>");
- if(!isnull(r_Response.toMap().get("details")))
- {
- if(!isnull(r_Response.toMap().get("details").get("output")))
- {
- v_AccessToken = r_Response.toMap().get("details").get("output");
- }
- }
- info v_AccessToken;
- //
- // build header
- m_Headers = Map();
- m_Headers.put("X-EBAY-API-SITEID",3);
- m_Headers.put("X-EBAY-API-COMPATIBILITY-LEVEL",v_TradingAPIVersion);
- m_Headers.put("X-EBAY-API-CALL-NAME",v_ApiCall);
- m_Headers.put("X-EBAY-API-IAF-TOKEN",v_AccessToken);
- //
- // build params
- m_Params = Map();
- m_Params.put("WarningLevel","High");
- m_Params.put("ErrorLanguage","en_GB");
- m_Params.put("ExternalPictureURL",v_PublicUrl);
- m_Params.put("PictureName","OhBeautifulMe");
- m_Params.put("PictureSet","Supersize");
- //
- // convert to xml and replace root nodes
- x_Params = m_Params.toXML();
- x_Params = x_Params.toString().replaceFirst("<root>","<?xml version=\"1.0\" encoding=\"utf-8\"?><" + v_ApiCall + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">");
- x_Params = x_Params.toString().replaceFirst("</root>","</" + v_ApiCall + "Request>");
- info x_Params;
- //
- // send the request XML as a string
- r_ResponseXML = invokeUrl
- [
- url :v_Endpoint
- type :POST
- parameters:x_Params
- headers:m_Headers
- ];
- info r_ResponseXML;
- //
- // parse response and retrieve the ebay URL of the hosted image
- if(r_ResponseXML.containsIgnoreCase("<Ack>Success</Ack>") || r_ResponseXML.containsIgnoreCase("<Ack>Warning</Ack>"))
- {
- v_BiggestHeight = 0;
- x_RespondedXML = r_ResponseXML.replaceFirst("xmlns=\"urn:ebay:apis:eBLBaseComponents\"","",true);
- x_PictureSets = x_RespondedXML.executeXPath("/"+v_ApiCall+"Response/SiteHostedPictureDetails/*").toXmlList();
- for each r_PictureSet in x_PictureSets
- {
- if(r_PictureSet.contains("PictureSetMember"))
- {
- // get image that is the biggest/tallest
- v_ThisHeight = r_PictureSet.executeXPath("/PictureSetMember/PictureHeight/text()").toLong();
- if(v_ThisHeight > v_BiggestHeight)
- {
- v_BiggestHeight = v_ThisHeight;
- v_EbayImageURL = r_PictureSet.executeXPath("/PictureSetMember/MemberURL/text()");
- }
- }
- }
- }
- return v_EbayImageURL;
- Save the CRM function
- Hover your mouse cursor over it and click on the ellipsis/3 dots
- Select the REST API
- Enable REST API
- Note the REST API function and you can now call it from wherever.
- Done
Additional Caveat(s)
- This currently does one image from Zoho Inventory. I'll need to add to the code to retrieve the other photos from Zoho Inventory
- Still to run a few tests but if I'm right in my guesses, if you edit the listing in eBay's interface, it may get confused as to where the photo came from and will error when displaying it. I think it will be fine.
Source(s)
- Joel's tired and inefficient brain
- The non-existent and plethora of forums not explaining how to attach a binary image to eBay
- eBay Developers Program - eBay Trading API v1247 - UploadSiteHostedPictures
- JoelLipman.Com - Zoho Creator: Public URL of an Image field / Upload to Shopify API
- JoelLipman.Com - Zoho Creator: Push to eBay Listings