[{"data":1,"prerenderedAt":2115},["ShallowReactive",2],{"page-\u002Fautomating-side-hustle-operations-with-apis\u002Fautomating-social-media-posting\u002Fsync-shopify-orders-to-google-sheets-via-api\u002F":3,"faq-schema-\u002Fautomating-side-hustle-operations-with-apis\u002Fautomating-social-media-posting\u002Fsync-shopify-orders-to-google-sheets-via-api\u002F":2097},{"id":4,"title":5,"body":6,"description":2090,"extension":2091,"meta":2092,"navigation":232,"path":2093,"seo":2094,"stem":2095,"__hash__":2096},"content\u002Fautomating-side-hustle-operations-with-apis\u002Fautomating-social-media-posting\u002Fsync-shopify-orders-to-google-sheets-via-api\u002Findex.md","Sync Shopify Orders to Google Sheets via API: Python Implementation Guide",{"type":7,"value":8,"toc":2081},"minimark",[9,13,23,29,45,48,53,56,108,143,149,151,155,162,810,820,822,826,837,1216,1230,1232,1236,1239,1877,1895,1897,1901,1904,1946,1948,1952,2003,2005,2009,2032,2042,2059,2077],[10,11,5],"h1",{"id":12},"sync-shopify-orders-to-google-sheets-via-api-python-implementation-guide",[14,15,16,17,22],"p",{},"Stop paying monthly SaaS fees for basic data routing. This guide delivers a direct, production-ready Python workflow to fetch Shopify orders and append them to Google Sheets using official REST APIs. By building a custom ",[18,19,21],"a",{"href":20},"\u002Fautomating-side-hustle-operations-with-apis\u002F","Automating Side-Hustle Operations with APIs"," pipeline, you bypass Zapier\u002FMake overhead while gaining full control over data transformation, error handling, and execution frequency.",[14,24,25],{},[26,27,28],"strong",{},"Key Implementation Advantages:",[30,31,32,36,39,42],"ul",{},[33,34,35],"li",{},"Eliminates third-party subscription costs with a headless, cron-driven script",[33,37,38],{},"Enforces Shopify Admin REST API v2024-01 strict 429 rate-limit handling",[33,40,41],{},"Leverages Google Sheets API v4 with service account authentication for zero-touch execution",[33,43,44],{},"Includes deduplication logic and incremental timestamp tracking to prevent data drift",[46,47],"hr",{},[49,50,52],"h2",{"id":51},"_1-api-credential-generation-scope-configuration","1. API Credential Generation & Scope Configuration",[14,54,55],{},"Secure, least-privilege access is non-negotiable for production data syncs. Follow this exact sequence to provision credentials:",[57,58,59,74,80,98],"ol",{},[33,60,61,64,65,69,70,73],{},[26,62,63],{},"Shopify Custom App:"," Navigate to ",[66,67,68],"code",{},"Settings > Apps and sales channels > Develop apps",". Create a new app, assign the ",[66,71,72],{},"read_orders"," Admin API scope, and install it. Copy the generated Admin API access token.",[33,75,76,79],{},[26,77,78],{},"Google Cloud Service Account:"," In GCP, create a Service Account. Enable the Google Sheets API, generate a JSON key, and download it.",[33,81,82,85,86,89,90,93,94,97],{},[26,83,84],{},"Sheet Permissions:"," Open your target Google Sheet, click ",[66,87,88],{},"Share",", and paste the service account email (",[66,91,92],{},"...@project-id.iam.gserviceaccount.com",") with ",[66,95,96],{},"Editor"," access.",[33,99,100,103,104,107],{},[26,101,102],{},"Environment Configuration:"," Store all sensitive values in a ",[66,105,106],{},".env"," file. Never hardcode credentials.",[109,110,115],"pre",{"className":111,"code":112,"language":113,"meta":114,"style":114},"language-env shiki shiki-themes github-light github-dark","SHOPIFY_STORE_URL=your-store.myshopify.com\nSHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxx\nGOOGLE_CREDS_PATH=.\u002Fservice-account.json\nGOOGLE_SHEET_ID=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms\n","env","",[66,116,117,125,131,137],{"__ignoreMap":114},[118,119,122],"span",{"class":120,"line":121},"line",1,[118,123,124],{},"SHOPIFY_STORE_URL=your-store.myshopify.com\n",[118,126,128],{"class":120,"line":127},2,[118,129,130],{},"SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxx\n",[118,132,134],{"class":120,"line":133},3,[118,135,136],{},"GOOGLE_CREDS_PATH=.\u002Fservice-account.json\n",[118,138,140],{"class":120,"line":139},4,[118,141,142],{},"GOOGLE_SHEET_ID=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms\n",[14,144,145,146],{},"Install dependencies: ",[66,147,148],{},"pip install requests google-api-python-client google-auth python-dotenv",[46,150],{},[49,152,154],{"id":153},"_2-shopify-order-fetching-with-pagination","2. Shopify Order Fetching with Pagination",[14,156,157,158,161],{},"Shopify's REST API paginates via ",[66,159,160],{},"Link"," headers and enforces a strict leaky-bucket rate limit. The following generator handles cursor-based pagination, automatic 429 backoff, and incremental filtering.",[109,163,167],{"className":164,"code":165,"language":166,"meta":114,"style":114},"language-python shiki shiki-themes github-light github-dark","import os\nimport time\nimport logging\nimport requests\nfrom requests.adapters import HTTPAdapter\nfrom urllib3.util.retry import Retry\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s - %(levelname)s - %(message)s\")\n\ndef get_shopify_session() -> requests.Session:\n session = requests.Session()\n retry_strategy = Retry(\n total=3, backoff_factor=2, status_forcelist=[429, 500, 502, 503, 504]\n )\n session.mount(\"https:\u002F\u002F\", HTTPAdapter(max_retries=retry_strategy))\n return session\n\ndef fetch_shopify_orders(shop_url: str, token: str, since_id: int | None = None):\n session = get_shopify_session()\n headers = {\"X-Shopify-Access-Token\": token}\n params = {\"status\": \"any\", \"limit\": 250}\n if since_id:\n params[\"since_id\"] = since_id\n\n url = f\"https:\u002F\u002F{shop_url}\u002Fadmin\u002Fapi\u002F2024-01\u002Forders.json\"\n \n while url:\n try:\n res = session.get(url, headers=headers, params=params, timeout=30)\n if res.status_code == 429:\n retry_after = int(res.headers.get(\"Retry-After\", 2))\n logging.warning(f\"Rate limited. Pausing for {retry_after}s...\")\n time.sleep(retry_after)\n continue\n res.raise_for_status()\n data = res.json()\n yield data.get(\"orders\", [])\n url = res.links.get(\"next\", {}).get(\"url\")\n except requests.exceptions.RequestException as e:\n logging.error(f\"Shopify API request failed: {e}\")\n break\n","python",[66,168,169,179,186,193,200,214,227,234,285,290,303,314,325,382,388,408,417,422,461,471,488,520,529,546,551,577,583,592,601,638,654,678,702,708,714,720,731,746,767,782,804],{"__ignoreMap":114},[118,170,171,175],{"class":120,"line":121},[118,172,174],{"class":173},"szBVR","import",[118,176,178],{"class":177},"sVt8B"," os\n",[118,180,181,183],{"class":120,"line":127},[118,182,174],{"class":173},[118,184,185],{"class":177}," time\n",[118,187,188,190],{"class":120,"line":133},[118,189,174],{"class":173},[118,191,192],{"class":177}," logging\n",[118,194,195,197],{"class":120,"line":139},[118,196,174],{"class":173},[118,198,199],{"class":177}," requests\n",[118,201,203,206,209,211],{"class":120,"line":202},5,[118,204,205],{"class":173},"from",[118,207,208],{"class":177}," requests.adapters ",[118,210,174],{"class":173},[118,212,213],{"class":177}," HTTPAdapter\n",[118,215,217,219,222,224],{"class":120,"line":216},6,[118,218,205],{"class":173},[118,220,221],{"class":177}," urllib3.util.retry ",[118,223,174],{"class":173},[118,225,226],{"class":177}," Retry\n",[118,228,230],{"class":120,"line":229},7,[118,231,233],{"emptyLinePlaceholder":232},true,"\n",[118,235,237,240,244,247,250,254,257,260,262,266,269,272,275,277,280,282],{"class":120,"line":236},8,[118,238,239],{"class":177},"logging.basicConfig(",[118,241,243],{"class":242},"s4XuR","level",[118,245,246],{"class":173},"=",[118,248,249],{"class":177},"logging.",[118,251,253],{"class":252},"sj4cs","INFO",[118,255,256],{"class":177},", ",[118,258,259],{"class":242},"format",[118,261,246],{"class":173},[118,263,265],{"class":264},"sZZnC","\"",[118,267,268],{"class":252},"%(asctime)s",[118,270,271],{"class":264}," - ",[118,273,274],{"class":252},"%(levelname)s",[118,276,271],{"class":264},[118,278,279],{"class":252},"%(message)s",[118,281,265],{"class":264},[118,283,284],{"class":177},")\n",[118,286,288],{"class":120,"line":287},9,[118,289,233],{"emptyLinePlaceholder":232},[118,291,293,296,300],{"class":120,"line":292},10,[118,294,295],{"class":173},"def",[118,297,299],{"class":298},"sScJk"," get_shopify_session",[118,301,302],{"class":177},"() -> requests.Session:\n",[118,304,306,309,311],{"class":120,"line":305},11,[118,307,308],{"class":177}," session ",[118,310,246],{"class":173},[118,312,313],{"class":177}," requests.Session()\n",[118,315,317,320,322],{"class":120,"line":316},12,[118,318,319],{"class":177}," retry_strategy ",[118,321,246],{"class":173},[118,323,324],{"class":177}," Retry(\n",[118,326,328,331,333,336,338,341,343,346,348,351,353,356,359,361,364,366,369,371,374,376,379],{"class":120,"line":327},13,[118,329,330],{"class":242}," total",[118,332,246],{"class":173},[118,334,335],{"class":252},"3",[118,337,256],{"class":177},[118,339,340],{"class":242},"backoff_factor",[118,342,246],{"class":173},[118,344,345],{"class":252},"2",[118,347,256],{"class":177},[118,349,350],{"class":242},"status_forcelist",[118,352,246],{"class":173},[118,354,355],{"class":177},"[",[118,357,358],{"class":252},"429",[118,360,256],{"class":177},[118,362,363],{"class":252},"500",[118,365,256],{"class":177},[118,367,368],{"class":252},"502",[118,370,256],{"class":177},[118,372,373],{"class":252},"503",[118,375,256],{"class":177},[118,377,378],{"class":252},"504",[118,380,381],{"class":177},"]\n",[118,383,385],{"class":120,"line":384},14,[118,386,387],{"class":177}," )\n",[118,389,391,394,397,400,403,405],{"class":120,"line":390},15,[118,392,393],{"class":177}," session.mount(",[118,395,396],{"class":264},"\"https:\u002F\u002F\"",[118,398,399],{"class":177},", HTTPAdapter(",[118,401,402],{"class":242},"max_retries",[118,404,246],{"class":173},[118,406,407],{"class":177},"retry_strategy))\n",[118,409,411,414],{"class":120,"line":410},16,[118,412,413],{"class":173}," return",[118,415,416],{"class":177}," session\n",[118,418,420],{"class":120,"line":419},17,[118,421,233],{"emptyLinePlaceholder":232},[118,423,425,427,430,433,436,439,441,444,447,450,453,456,458],{"class":120,"line":424},18,[118,426,295],{"class":173},[118,428,429],{"class":298}," fetch_shopify_orders",[118,431,432],{"class":177},"(shop_url: ",[118,434,435],{"class":252},"str",[118,437,438],{"class":177},", token: ",[118,440,435],{"class":252},[118,442,443],{"class":177},", since_id: ",[118,445,446],{"class":252},"int",[118,448,449],{"class":173}," |",[118,451,452],{"class":252}," None",[118,454,455],{"class":173}," =",[118,457,452],{"class":252},[118,459,460],{"class":177},"):\n",[118,462,464,466,468],{"class":120,"line":463},19,[118,465,308],{"class":177},[118,467,246],{"class":173},[118,469,470],{"class":177}," get_shopify_session()\n",[118,472,474,477,479,482,485],{"class":120,"line":473},20,[118,475,476],{"class":177}," headers ",[118,478,246],{"class":173},[118,480,481],{"class":177}," {",[118,483,484],{"class":264},"\"X-Shopify-Access-Token\"",[118,486,487],{"class":177},": token}\n",[118,489,491,494,496,498,501,504,507,509,512,514,517],{"class":120,"line":490},21,[118,492,493],{"class":177}," params ",[118,495,246],{"class":173},[118,497,481],{"class":177},[118,499,500],{"class":264},"\"status\"",[118,502,503],{"class":177},": ",[118,505,506],{"class":264},"\"any\"",[118,508,256],{"class":177},[118,510,511],{"class":264},"\"limit\"",[118,513,503],{"class":177},[118,515,516],{"class":252},"250",[118,518,519],{"class":177},"}\n",[118,521,523,526],{"class":120,"line":522},22,[118,524,525],{"class":173}," if",[118,527,528],{"class":177}," since_id:\n",[118,530,532,535,538,541,543],{"class":120,"line":531},23,[118,533,534],{"class":177}," params[",[118,536,537],{"class":264},"\"since_id\"",[118,539,540],{"class":177},"] ",[118,542,246],{"class":173},[118,544,545],{"class":177}," since_id\n",[118,547,549],{"class":120,"line":548},24,[118,550,233],{"emptyLinePlaceholder":232},[118,552,554,557,559,562,565,568,571,574],{"class":120,"line":553},25,[118,555,556],{"class":177}," url ",[118,558,246],{"class":173},[118,560,561],{"class":173}," f",[118,563,564],{"class":264},"\"https:\u002F\u002F",[118,566,567],{"class":252},"{",[118,569,570],{"class":177},"shop_url",[118,572,573],{"class":252},"}",[118,575,576],{"class":264},"\u002Fadmin\u002Fapi\u002F2024-01\u002Forders.json\"\n",[118,578,580],{"class":120,"line":579},26,[118,581,582],{"class":177}," \n",[118,584,586,589],{"class":120,"line":585},27,[118,587,588],{"class":173}," while",[118,590,591],{"class":177}," url:\n",[118,593,595,598],{"class":120,"line":594},28,[118,596,597],{"class":173}," try",[118,599,600],{"class":177},":\n",[118,602,604,607,609,612,615,617,620,623,625,628,631,633,636],{"class":120,"line":603},29,[118,605,606],{"class":177}," res ",[118,608,246],{"class":173},[118,610,611],{"class":177}," session.get(url, ",[118,613,614],{"class":242},"headers",[118,616,246],{"class":173},[118,618,619],{"class":177},"headers, ",[118,621,622],{"class":242},"params",[118,624,246],{"class":173},[118,626,627],{"class":177},"params, ",[118,629,630],{"class":242},"timeout",[118,632,246],{"class":173},[118,634,635],{"class":252},"30",[118,637,284],{"class":177},[118,639,641,643,646,649,652],{"class":120,"line":640},30,[118,642,525],{"class":173},[118,644,645],{"class":177}," res.status_code ",[118,647,648],{"class":173},"==",[118,650,651],{"class":252}," 429",[118,653,600],{"class":177},[118,655,657,660,662,665,668,671,673,675],{"class":120,"line":656},31,[118,658,659],{"class":177}," retry_after ",[118,661,246],{"class":173},[118,663,664],{"class":252}," int",[118,666,667],{"class":177},"(res.headers.get(",[118,669,670],{"class":264},"\"Retry-After\"",[118,672,256],{"class":177},[118,674,345],{"class":252},[118,676,677],{"class":177},"))\n",[118,679,681,684,687,690,692,695,697,700],{"class":120,"line":680},32,[118,682,683],{"class":177}," logging.warning(",[118,685,686],{"class":173},"f",[118,688,689],{"class":264},"\"Rate limited. Pausing for ",[118,691,567],{"class":252},[118,693,694],{"class":177},"retry_after",[118,696,573],{"class":252},[118,698,699],{"class":264},"s...\"",[118,701,284],{"class":177},[118,703,705],{"class":120,"line":704},33,[118,706,707],{"class":177}," time.sleep(retry_after)\n",[118,709,711],{"class":120,"line":710},34,[118,712,713],{"class":173}," continue\n",[118,715,717],{"class":120,"line":716},35,[118,718,719],{"class":177}," res.raise_for_status()\n",[118,721,723,726,728],{"class":120,"line":722},36,[118,724,725],{"class":177}," data ",[118,727,246],{"class":173},[118,729,730],{"class":177}," res.json()\n",[118,732,734,737,740,743],{"class":120,"line":733},37,[118,735,736],{"class":173}," yield",[118,738,739],{"class":177}," data.get(",[118,741,742],{"class":264},"\"orders\"",[118,744,745],{"class":177},", [])\n",[118,747,749,751,753,756,759,762,765],{"class":120,"line":748},38,[118,750,556],{"class":177},[118,752,246],{"class":173},[118,754,755],{"class":177}," res.links.get(",[118,757,758],{"class":264},"\"next\"",[118,760,761],{"class":177},", {}).get(",[118,763,764],{"class":264},"\"url\"",[118,766,284],{"class":177},[118,768,770,773,776,779],{"class":120,"line":769},39,[118,771,772],{"class":173}," except",[118,774,775],{"class":177}," requests.exceptions.RequestException ",[118,777,778],{"class":173},"as",[118,780,781],{"class":177}," e:\n",[118,783,785,788,790,793,795,798,800,802],{"class":120,"line":784},40,[118,786,787],{"class":177}," logging.error(",[118,789,686],{"class":173},[118,791,792],{"class":264},"\"Shopify API request failed: ",[118,794,567],{"class":252},[118,796,797],{"class":177},"e",[118,799,573],{"class":252},[118,801,265],{"class":264},[118,803,284],{"class":177},[118,805,807],{"class":120,"line":806},41,[118,808,809],{"class":173}," break\n",[14,811,812,815,816,819],{},[26,813,814],{},"Why this works:"," The generator yields batches without loading everything into memory. The ",[66,817,818],{},"Retry-After"," header parsing ensures compliance with Shopify's 40 req\u002Fsec sliding window.",[46,821],{},[49,823,825],{"id":824},"_3-google-sheets-v4-append-logic","3. Google Sheets v4 Append Logic",[14,827,828,829,832,833,836],{},"Pushing data to Sheets requires authenticated service account credentials and strict payload formatting. The ",[66,830,831],{},"values().append()"," endpoint with ",[66,834,835],{},"INSERT_ROWS"," prevents overwrites and preserves existing formulas.",[109,838,840],{"className":164,"code":839,"language":166,"meta":114,"style":114},"from google.oauth2 import service_account\nfrom googleapiclient.discovery import build\nfrom googleapiclient.errors import HttpError\n\nSCOPES = [\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fspreadsheets\"]\n\ndef get_sheets_service():\n creds_path = os.getenv(\"GOOGLE_CREDS_PATH\")\n if not creds_path or not os.path.exists(creds_path):\n raise FileNotFoundError(\"Google credentials file not found.\")\n creds = service_account.Credentials.from_service_account_file(creds_path, scopes=SCOPES)\n return build(\"sheets\", \"v4\", credentials=creds)\n\ndef append_to_sheets(sheet_id: str, range_name: str, values: list[list]):\n if not values:\n return\n service = get_sheets_service()\n body = {\"values\": values}\n \n try:\n service.spreadsheets().values().append(\n spreadsheetId=sheet_id,\n range=range_name,\n valueInputOption=\"RAW\",\n insertDataOption=\"INSERT_ROWS\",\n body=body,\n ).execute()\n logging.info(f\"Successfully appended {len(values)} rows to {range_name}\")\n except HttpError as err:\n logging.error(f\"Google Sheets API error: {err.reason}\")\n raise\n",[66,841,842,854,866,878,882,897,901,911,926,943,959,978,1003,1007,1033,1042,1047,1057,1072,1076,1082,1087,1097,1107,1120,1132,1142,1147,1179,1191,1211],{"__ignoreMap":114},[118,843,844,846,849,851],{"class":120,"line":121},[118,845,205],{"class":173},[118,847,848],{"class":177}," google.oauth2 ",[118,850,174],{"class":173},[118,852,853],{"class":177}," service_account\n",[118,855,856,858,861,863],{"class":120,"line":127},[118,857,205],{"class":173},[118,859,860],{"class":177}," googleapiclient.discovery ",[118,862,174],{"class":173},[118,864,865],{"class":177}," build\n",[118,867,868,870,873,875],{"class":120,"line":133},[118,869,205],{"class":173},[118,871,872],{"class":177}," googleapiclient.errors ",[118,874,174],{"class":173},[118,876,877],{"class":177}," HttpError\n",[118,879,880],{"class":120,"line":139},[118,881,233],{"emptyLinePlaceholder":232},[118,883,884,887,889,892,895],{"class":120,"line":202},[118,885,886],{"class":252},"SCOPES",[118,888,455],{"class":173},[118,890,891],{"class":177}," [",[118,893,894],{"class":264},"\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fspreadsheets\"",[118,896,381],{"class":177},[118,898,899],{"class":120,"line":216},[118,900,233],{"emptyLinePlaceholder":232},[118,902,903,905,908],{"class":120,"line":229},[118,904,295],{"class":173},[118,906,907],{"class":298}," get_sheets_service",[118,909,910],{"class":177},"():\n",[118,912,913,916,918,921,924],{"class":120,"line":236},[118,914,915],{"class":177}," creds_path ",[118,917,246],{"class":173},[118,919,920],{"class":177}," os.getenv(",[118,922,923],{"class":264},"\"GOOGLE_CREDS_PATH\"",[118,925,284],{"class":177},[118,927,928,930,933,935,938,940],{"class":120,"line":287},[118,929,525],{"class":173},[118,931,932],{"class":173}," not",[118,934,915],{"class":177},[118,936,937],{"class":173},"or",[118,939,932],{"class":173},[118,941,942],{"class":177}," os.path.exists(creds_path):\n",[118,944,945,948,951,954,957],{"class":120,"line":292},[118,946,947],{"class":173}," raise",[118,949,950],{"class":252}," FileNotFoundError",[118,952,953],{"class":177},"(",[118,955,956],{"class":264},"\"Google credentials file not found.\"",[118,958,284],{"class":177},[118,960,961,964,966,969,972,974,976],{"class":120,"line":305},[118,962,963],{"class":177}," creds ",[118,965,246],{"class":173},[118,967,968],{"class":177}," service_account.Credentials.from_service_account_file(creds_path, ",[118,970,971],{"class":242},"scopes",[118,973,246],{"class":173},[118,975,886],{"class":252},[118,977,284],{"class":177},[118,979,980,982,985,988,990,993,995,998,1000],{"class":120,"line":316},[118,981,413],{"class":173},[118,983,984],{"class":177}," build(",[118,986,987],{"class":264},"\"sheets\"",[118,989,256],{"class":177},[118,991,992],{"class":264},"\"v4\"",[118,994,256],{"class":177},[118,996,997],{"class":242},"credentials",[118,999,246],{"class":173},[118,1001,1002],{"class":177},"creds)\n",[118,1004,1005],{"class":120,"line":327},[118,1006,233],{"emptyLinePlaceholder":232},[118,1008,1009,1011,1014,1017,1019,1022,1024,1027,1030],{"class":120,"line":384},[118,1010,295],{"class":173},[118,1012,1013],{"class":298}," append_to_sheets",[118,1015,1016],{"class":177},"(sheet_id: ",[118,1018,435],{"class":252},[118,1020,1021],{"class":177},", range_name: ",[118,1023,435],{"class":252},[118,1025,1026],{"class":177},", values: list[",[118,1028,1029],{"class":252},"list",[118,1031,1032],{"class":177},"]):\n",[118,1034,1035,1037,1039],{"class":120,"line":390},[118,1036,525],{"class":173},[118,1038,932],{"class":173},[118,1040,1041],{"class":177}," values:\n",[118,1043,1044],{"class":120,"line":410},[118,1045,1046],{"class":173}," return\n",[118,1048,1049,1052,1054],{"class":120,"line":419},[118,1050,1051],{"class":177}," service ",[118,1053,246],{"class":173},[118,1055,1056],{"class":177}," get_sheets_service()\n",[118,1058,1059,1062,1064,1066,1069],{"class":120,"line":424},[118,1060,1061],{"class":177}," body ",[118,1063,246],{"class":173},[118,1065,481],{"class":177},[118,1067,1068],{"class":264},"\"values\"",[118,1070,1071],{"class":177},": values}\n",[118,1073,1074],{"class":120,"line":463},[118,1075,582],{"class":177},[118,1077,1078,1080],{"class":120,"line":473},[118,1079,597],{"class":173},[118,1081,600],{"class":177},[118,1083,1084],{"class":120,"line":490},[118,1085,1086],{"class":177}," service.spreadsheets().values().append(\n",[118,1088,1089,1092,1094],{"class":120,"line":522},[118,1090,1091],{"class":242}," spreadsheetId",[118,1093,246],{"class":173},[118,1095,1096],{"class":177},"sheet_id,\n",[118,1098,1099,1102,1104],{"class":120,"line":531},[118,1100,1101],{"class":242}," range",[118,1103,246],{"class":173},[118,1105,1106],{"class":177},"range_name,\n",[118,1108,1109,1112,1114,1117],{"class":120,"line":548},[118,1110,1111],{"class":242}," valueInputOption",[118,1113,246],{"class":173},[118,1115,1116],{"class":264},"\"RAW\"",[118,1118,1119],{"class":177},",\n",[118,1121,1122,1125,1127,1130],{"class":120,"line":553},[118,1123,1124],{"class":242}," insertDataOption",[118,1126,246],{"class":173},[118,1128,1129],{"class":264},"\"INSERT_ROWS\"",[118,1131,1119],{"class":177},[118,1133,1134,1137,1139],{"class":120,"line":579},[118,1135,1136],{"class":242}," body",[118,1138,246],{"class":173},[118,1140,1141],{"class":177},"body,\n",[118,1143,1144],{"class":120,"line":585},[118,1145,1146],{"class":177}," ).execute()\n",[118,1148,1149,1152,1154,1157,1160,1163,1165,1168,1170,1173,1175,1177],{"class":120,"line":594},[118,1150,1151],{"class":177}," logging.info(",[118,1153,686],{"class":173},[118,1155,1156],{"class":264},"\"Successfully appended ",[118,1158,1159],{"class":252},"{len",[118,1161,1162],{"class":177},"(values)",[118,1164,573],{"class":252},[118,1166,1167],{"class":264}," rows to ",[118,1169,567],{"class":252},[118,1171,1172],{"class":177},"range_name",[118,1174,573],{"class":252},[118,1176,265],{"class":264},[118,1178,284],{"class":177},[118,1180,1181,1183,1186,1188],{"class":120,"line":603},[118,1182,772],{"class":173},[118,1184,1185],{"class":177}," HttpError ",[118,1187,778],{"class":173},[118,1189,1190],{"class":177}," err:\n",[118,1192,1193,1195,1197,1200,1202,1205,1207,1209],{"class":120,"line":640},[118,1194,787],{"class":177},[118,1196,686],{"class":173},[118,1198,1199],{"class":264},"\"Google Sheets API error: ",[118,1201,567],{"class":252},[118,1203,1204],{"class":177},"err.reason",[118,1206,573],{"class":252},[118,1208,265],{"class":264},[118,1210,284],{"class":177},[118,1212,1213],{"class":120,"line":656},[118,1214,1215],{"class":173}," raise\n",[14,1217,1218,1221,1222,1225,1226,1229],{},[26,1219,1220],{},"Implementation Notes:"," ",[66,1223,1224],{},"valueInputOption='RAW'"," prevents Sheets from auto-formatting dates or numbers. Always validate that each row matches your header column count before calling ",[66,1227,1228],{},"append()",".",[46,1231],{},[49,1233,1235],{"id":1234},"_4-building-the-incremental-sync-engine","4. Building the Incremental Sync Engine",[14,1237,1238],{},"A reliable sync requires state persistence, deduplication, and structured orchestration. This engine tracks processed order IDs locally, flattens nested JSON payloads, and triggers downstream workflows.",[109,1240,1242],{"className":164,"code":1241,"language":166,"meta":114,"style":114},"import json\nimport os\nfrom datetime import datetime\n\nSTATE_FILE = \"sync_state.json\"\n\ndef load_state() -> dict:\n if not os.path.exists(STATE_FILE):\n return {\"last_id\": None, \"processed_ids\": []}\n with open(STATE_FILE, \"r\") as f:\n return json.load(f)\n\ndef save_state(state: dict):\n with open(STATE_FILE, \"w\") as f:\n json.dump(state, f, indent=2)\n\ndef flatten_order(order: dict) -> list:\n return [\n order.get(\"id\"),\n order.get(\"created_at\"),\n order.get(\"total_price\"),\n order.get(\"financial_status\", \"unknown\"),\n order.get(\"customer\", {}).get(\"email\", \"guest\"),\n len(order.get(\"line_items\", [])),\n ]\n\ndef run_sync():\n state = load_state()\n shop_url = os.getenv(\"SHOPIFY_STORE_URL\")\n token = os.getenv(\"SHOPIFY_ACCESS_TOKEN\")\n sheet_id = os.getenv(\"GOOGLE_SHEET_ID\")\n range_name = \"Orders!A:F\"\n\n new_rows = []\n for batch in fetch_shopify_orders(shop_url, token, state.get(\"last_id\")):\n processed_set = set(str(pid) for pid in state.get(\"processed_ids\", []))\n for order in batch:\n if str(order[\"id\"]) not in processed_set:\n new_rows.append(flatten_order(order))\n\n if new_rows:\n append_to_sheets(sheet_id, range_name, new_rows)\n state[\"last_id\"] = new_rows[-1][0]\n state[\"processed_ids\"].extend([str(row[0]) for row in new_rows])\n save_state(state)\n logging.info(f\"Sync complete. {len(new_rows)} new orders processed.\")\n # Trigger downstream workflows like [Automating Social Media Posting](\u002Fautomating-side-hustle-operations-with-apis\u002Fautomating-social-media-posting\u002F) \n # once data lands successfully.\n else:\n logging.info(\"No new orders to sync.\")\n\nif __name__ == \"__main__\":\n run_sync()\n",[66,1243,1244,1251,1257,1269,1273,1283,1287,1302,1315,1337,1362,1369,1373,1387,1408,1422,1426,1445,1452,1463,1472,1481,1495,1514,1528,1533,1537,1546,1556,1570,1584,1598,1608,1612,1622,1641,1674,1686,1710,1715,1719,1726,1732,1761,1790,1796,1818,1825,1831,1839,1849,1854,1871],{"__ignoreMap":114},[118,1245,1246,1248],{"class":120,"line":121},[118,1247,174],{"class":173},[118,1249,1250],{"class":177}," json\n",[118,1252,1253,1255],{"class":120,"line":127},[118,1254,174],{"class":173},[118,1256,178],{"class":177},[118,1258,1259,1261,1264,1266],{"class":120,"line":133},[118,1260,205],{"class":173},[118,1262,1263],{"class":177}," datetime ",[118,1265,174],{"class":173},[118,1267,1268],{"class":177}," datetime\n",[118,1270,1271],{"class":120,"line":139},[118,1272,233],{"emptyLinePlaceholder":232},[118,1274,1275,1278,1280],{"class":120,"line":202},[118,1276,1277],{"class":252},"STATE_FILE",[118,1279,455],{"class":173},[118,1281,1282],{"class":264}," \"sync_state.json\"\n",[118,1284,1285],{"class":120,"line":216},[118,1286,233],{"emptyLinePlaceholder":232},[118,1288,1289,1291,1294,1297,1300],{"class":120,"line":229},[118,1290,295],{"class":173},[118,1292,1293],{"class":298}," load_state",[118,1295,1296],{"class":177},"() -> ",[118,1298,1299],{"class":252},"dict",[118,1301,600],{"class":177},[118,1303,1304,1306,1308,1311,1313],{"class":120,"line":236},[118,1305,525],{"class":173},[118,1307,932],{"class":173},[118,1309,1310],{"class":177}," os.path.exists(",[118,1312,1277],{"class":252},[118,1314,460],{"class":177},[118,1316,1317,1319,1321,1324,1326,1329,1331,1334],{"class":120,"line":287},[118,1318,413],{"class":173},[118,1320,481],{"class":177},[118,1322,1323],{"class":264},"\"last_id\"",[118,1325,503],{"class":177},[118,1327,1328],{"class":252},"None",[118,1330,256],{"class":177},[118,1332,1333],{"class":264},"\"processed_ids\"",[118,1335,1336],{"class":177},": []}\n",[118,1338,1339,1342,1345,1347,1349,1351,1354,1357,1359],{"class":120,"line":292},[118,1340,1341],{"class":173}," with",[118,1343,1344],{"class":252}," open",[118,1346,953],{"class":177},[118,1348,1277],{"class":252},[118,1350,256],{"class":177},[118,1352,1353],{"class":264},"\"r\"",[118,1355,1356],{"class":177},") ",[118,1358,778],{"class":173},[118,1360,1361],{"class":177}," f:\n",[118,1363,1364,1366],{"class":120,"line":305},[118,1365,413],{"class":173},[118,1367,1368],{"class":177}," json.load(f)\n",[118,1370,1371],{"class":120,"line":316},[118,1372,233],{"emptyLinePlaceholder":232},[118,1374,1375,1377,1380,1383,1385],{"class":120,"line":327},[118,1376,295],{"class":173},[118,1378,1379],{"class":298}," save_state",[118,1381,1382],{"class":177},"(state: ",[118,1384,1299],{"class":252},[118,1386,460],{"class":177},[118,1388,1389,1391,1393,1395,1397,1399,1402,1404,1406],{"class":120,"line":384},[118,1390,1341],{"class":173},[118,1392,1344],{"class":252},[118,1394,953],{"class":177},[118,1396,1277],{"class":252},[118,1398,256],{"class":177},[118,1400,1401],{"class":264},"\"w\"",[118,1403,1356],{"class":177},[118,1405,778],{"class":173},[118,1407,1361],{"class":177},[118,1409,1410,1413,1416,1418,1420],{"class":120,"line":390},[118,1411,1412],{"class":177}," json.dump(state, f, ",[118,1414,1415],{"class":242},"indent",[118,1417,246],{"class":173},[118,1419,345],{"class":252},[118,1421,284],{"class":177},[118,1423,1424],{"class":120,"line":410},[118,1425,233],{"emptyLinePlaceholder":232},[118,1427,1428,1430,1433,1436,1438,1441,1443],{"class":120,"line":419},[118,1429,295],{"class":173},[118,1431,1432],{"class":298}," flatten_order",[118,1434,1435],{"class":177},"(order: ",[118,1437,1299],{"class":252},[118,1439,1440],{"class":177},") -> ",[118,1442,1029],{"class":252},[118,1444,600],{"class":177},[118,1446,1447,1449],{"class":120,"line":424},[118,1448,413],{"class":173},[118,1450,1451],{"class":177}," [\n",[118,1453,1454,1457,1460],{"class":120,"line":463},[118,1455,1456],{"class":177}," order.get(",[118,1458,1459],{"class":264},"\"id\"",[118,1461,1462],{"class":177},"),\n",[118,1464,1465,1467,1470],{"class":120,"line":473},[118,1466,1456],{"class":177},[118,1468,1469],{"class":264},"\"created_at\"",[118,1471,1462],{"class":177},[118,1473,1474,1476,1479],{"class":120,"line":490},[118,1475,1456],{"class":177},[118,1477,1478],{"class":264},"\"total_price\"",[118,1480,1462],{"class":177},[118,1482,1483,1485,1488,1490,1493],{"class":120,"line":522},[118,1484,1456],{"class":177},[118,1486,1487],{"class":264},"\"financial_status\"",[118,1489,256],{"class":177},[118,1491,1492],{"class":264},"\"unknown\"",[118,1494,1462],{"class":177},[118,1496,1497,1499,1502,1504,1507,1509,1512],{"class":120,"line":531},[118,1498,1456],{"class":177},[118,1500,1501],{"class":264},"\"customer\"",[118,1503,761],{"class":177},[118,1505,1506],{"class":264},"\"email\"",[118,1508,256],{"class":177},[118,1510,1511],{"class":264},"\"guest\"",[118,1513,1462],{"class":177},[118,1515,1516,1519,1522,1525],{"class":120,"line":548},[118,1517,1518],{"class":252}," len",[118,1520,1521],{"class":177},"(order.get(",[118,1523,1524],{"class":264},"\"line_items\"",[118,1526,1527],{"class":177},", [])),\n",[118,1529,1530],{"class":120,"line":553},[118,1531,1532],{"class":177}," ]\n",[118,1534,1535],{"class":120,"line":579},[118,1536,233],{"emptyLinePlaceholder":232},[118,1538,1539,1541,1544],{"class":120,"line":585},[118,1540,295],{"class":173},[118,1542,1543],{"class":298}," run_sync",[118,1545,910],{"class":177},[118,1547,1548,1551,1553],{"class":120,"line":594},[118,1549,1550],{"class":177}," state ",[118,1552,246],{"class":173},[118,1554,1555],{"class":177}," load_state()\n",[118,1557,1558,1561,1563,1565,1568],{"class":120,"line":603},[118,1559,1560],{"class":177}," shop_url ",[118,1562,246],{"class":173},[118,1564,920],{"class":177},[118,1566,1567],{"class":264},"\"SHOPIFY_STORE_URL\"",[118,1569,284],{"class":177},[118,1571,1572,1575,1577,1579,1582],{"class":120,"line":640},[118,1573,1574],{"class":177}," token ",[118,1576,246],{"class":173},[118,1578,920],{"class":177},[118,1580,1581],{"class":264},"\"SHOPIFY_ACCESS_TOKEN\"",[118,1583,284],{"class":177},[118,1585,1586,1589,1591,1593,1596],{"class":120,"line":656},[118,1587,1588],{"class":177}," sheet_id ",[118,1590,246],{"class":173},[118,1592,920],{"class":177},[118,1594,1595],{"class":264},"\"GOOGLE_SHEET_ID\"",[118,1597,284],{"class":177},[118,1599,1600,1603,1605],{"class":120,"line":680},[118,1601,1602],{"class":177}," range_name ",[118,1604,246],{"class":173},[118,1606,1607],{"class":264}," \"Orders!A:F\"\n",[118,1609,1610],{"class":120,"line":704},[118,1611,233],{"emptyLinePlaceholder":232},[118,1613,1614,1617,1619],{"class":120,"line":710},[118,1615,1616],{"class":177}," new_rows ",[118,1618,246],{"class":173},[118,1620,1621],{"class":177}," []\n",[118,1623,1624,1627,1630,1633,1636,1638],{"class":120,"line":716},[118,1625,1626],{"class":173}," for",[118,1628,1629],{"class":177}," batch ",[118,1631,1632],{"class":173},"in",[118,1634,1635],{"class":177}," fetch_shopify_orders(shop_url, token, state.get(",[118,1637,1323],{"class":264},[118,1639,1640],{"class":177},")):\n",[118,1642,1643,1646,1648,1651,1653,1655,1658,1661,1664,1666,1669,1671],{"class":120,"line":722},[118,1644,1645],{"class":177}," processed_set ",[118,1647,246],{"class":173},[118,1649,1650],{"class":252}," set",[118,1652,953],{"class":177},[118,1654,435],{"class":252},[118,1656,1657],{"class":177},"(pid) ",[118,1659,1660],{"class":173},"for",[118,1662,1663],{"class":177}," pid ",[118,1665,1632],{"class":173},[118,1667,1668],{"class":177}," state.get(",[118,1670,1333],{"class":264},[118,1672,1673],{"class":177},", []))\n",[118,1675,1676,1678,1681,1683],{"class":120,"line":733},[118,1677,1626],{"class":173},[118,1679,1680],{"class":177}," order ",[118,1682,1632],{"class":173},[118,1684,1685],{"class":177}," batch:\n",[118,1687,1688,1690,1693,1696,1698,1701,1704,1707],{"class":120,"line":748},[118,1689,525],{"class":173},[118,1691,1692],{"class":252}," str",[118,1694,1695],{"class":177},"(order[",[118,1697,1459],{"class":264},[118,1699,1700],{"class":177},"]) ",[118,1702,1703],{"class":173},"not",[118,1705,1706],{"class":173}," in",[118,1708,1709],{"class":177}," processed_set:\n",[118,1711,1712],{"class":120,"line":769},[118,1713,1714],{"class":177}," new_rows.append(flatten_order(order))\n",[118,1716,1717],{"class":120,"line":784},[118,1718,233],{"emptyLinePlaceholder":232},[118,1720,1721,1723],{"class":120,"line":806},[118,1722,525],{"class":173},[118,1724,1725],{"class":177}," new_rows:\n",[118,1727,1729],{"class":120,"line":1728},42,[118,1730,1731],{"class":177}," append_to_sheets(sheet_id, range_name, new_rows)\n",[118,1733,1735,1738,1740,1742,1744,1747,1750,1753,1756,1759],{"class":120,"line":1734},43,[118,1736,1737],{"class":177}," state[",[118,1739,1323],{"class":264},[118,1741,540],{"class":177},[118,1743,246],{"class":173},[118,1745,1746],{"class":177}," new_rows[",[118,1748,1749],{"class":173},"-",[118,1751,1752],{"class":252},"1",[118,1754,1755],{"class":177},"][",[118,1757,1758],{"class":252},"0",[118,1760,381],{"class":177},[118,1762,1764,1766,1768,1771,1773,1776,1778,1780,1782,1785,1787],{"class":120,"line":1763},44,[118,1765,1737],{"class":177},[118,1767,1333],{"class":264},[118,1769,1770],{"class":177},"].extend([",[118,1772,435],{"class":252},[118,1774,1775],{"class":177},"(row[",[118,1777,1758],{"class":252},[118,1779,1700],{"class":177},[118,1781,1660],{"class":173},[118,1783,1784],{"class":177}," row ",[118,1786,1632],{"class":173},[118,1788,1789],{"class":177}," new_rows])\n",[118,1791,1793],{"class":120,"line":1792},45,[118,1794,1795],{"class":177}," save_state(state)\n",[118,1797,1799,1801,1803,1806,1808,1811,1813,1816],{"class":120,"line":1798},46,[118,1800,1151],{"class":177},[118,1802,686],{"class":173},[118,1804,1805],{"class":264},"\"Sync complete. ",[118,1807,1159],{"class":252},[118,1809,1810],{"class":177},"(new_rows)",[118,1812,573],{"class":252},[118,1814,1815],{"class":264}," new orders processed.\"",[118,1817,284],{"class":177},[118,1819,1821],{"class":120,"line":1820},47,[118,1822,1824],{"class":1823},"sJ8bj"," # Trigger downstream workflows like [Automating Social Media Posting](\u002Fautomating-side-hustle-operations-with-apis\u002Fautomating-social-media-posting\u002F) \n",[118,1826,1828],{"class":120,"line":1827},48,[118,1829,1830],{"class":1823}," # once data lands successfully.\n",[118,1832,1834,1837],{"class":120,"line":1833},49,[118,1835,1836],{"class":173}," else",[118,1838,600],{"class":177},[118,1840,1842,1844,1847],{"class":120,"line":1841},50,[118,1843,1151],{"class":177},[118,1845,1846],{"class":264},"\"No new orders to sync.\"",[118,1848,284],{"class":177},[118,1850,1852],{"class":120,"line":1851},51,[118,1853,233],{"emptyLinePlaceholder":232},[118,1855,1857,1860,1863,1866,1869],{"class":120,"line":1856},52,[118,1858,1859],{"class":173},"if",[118,1861,1862],{"class":252}," __name__",[118,1864,1865],{"class":173}," ==",[118,1867,1868],{"class":264}," \"__main__\"",[118,1870,600],{"class":177},[118,1872,1874],{"class":120,"line":1873},53,[118,1875,1876],{"class":177}," run_sync()\n",[14,1878,1879,1882,1883,1886,1887,1890,1891,1894],{},[26,1880,1881],{},"State Management:"," The ",[66,1884,1885],{},"sync_state.json"," file acts as a lightweight checkpoint. Cross-referencing ",[66,1888,1889],{},"order.id"," against ",[66,1892,1893],{},"processed_ids"," guarantees idempotency across retries or manual runs.",[46,1896],{},[49,1898,1900],{"id":1899},"_5-serverless-deployment-scheduling","5. Serverless Deployment & Scheduling",[14,1902,1903],{},"Transitioning from a local script to a zero-maintenance execution environment requires containerization and structured logging.",[57,1905,1906,1920,1926,1932],{},[33,1907,1908,1911,1912,1915,1916,1919],{},[26,1909,1910],{},"Containerize:"," Wrap the script in a lightweight Docker image. Use ",[66,1913,1914],{},"python:3.11-slim"," and copy only ",[66,1917,1918],{},"requirements.txt"," and your script.",[33,1921,1922,1925],{},[26,1923,1924],{},"Deploy:"," Push to AWS Lambda (via AWS SAM\u002FServerless Framework) or Google Cloud Run. Both support cron-driven invocations.",[33,1927,1928,1931],{},[26,1929,1930],{},"Schedule:"," Use CloudWatch Events or Cloud Scheduler to trigger the container every 15–60 minutes. Align frequency with your store's order volume to stay within API quotas.",[33,1933,1934,1937,1938,1941,1942,1945],{},[26,1935,1936],{},"Observability:"," Replace ",[66,1939,1940],{},"print()"," with JSON-formatted structured logging. Pipe logs to CloudWatch\u002FStackdriver and configure alerts for ",[66,1943,1944],{},"ERROR"," level events or repeated 429 responses.",[46,1947],{},[49,1949,1951],{"id":1950},"common-mistakes","Common Mistakes",[30,1953,1954,1960,1969,1983,1989],{},[33,1955,1956,1959],{},[26,1957,1958],{},"Hardcoding tokens in scripts:"," Always use environment variables or a secrets manager. Exposed tokens lead to immediate store compromise.",[33,1961,1962,1965,1966,1968],{},[26,1963,1964],{},"Ignoring Shopify's sliding window rate limit:"," The 40 req\u002Fsec limit applies per store. Failing to parse ",[66,1967,818],{}," or implement exponential backoff triggers immediate 429 bans.",[33,1970,1971,1974,1975,1978,1979,1982],{},[26,1972,1973],{},"Overwriting sheet ranges:"," Using ",[66,1976,1977],{},"spreadsheets().values().update()"," instead of ",[66,1980,1981],{},".append()"," destroys historical data and breaks downstream formulas.",[33,1984,1985,1988],{},[26,1986,1987],{},"Failing to normalize timestamps:"," Shopify returns ISO 8601 strings. Always parse to UTC before comparison to avoid timezone drift during incremental syncs.",[33,1990,1991,1994,1995,1998,1999,2002],{},[26,1992,1993],{},"Running full historical pulls daily:"," Without ",[66,1996,1997],{},"since_id"," or ",[66,2000,2001],{},"updated_at_min"," tracking, you waste API quota and risk duplicate row insertion.",[46,2004],{},[49,2006,2008],{"id":2007},"faq","FAQ",[14,2010,2011,2014,2015,2017,2018,2020,2021,2024,2025,1998,2028,2031],{},[26,2012,2013],{},"How do I handle Shopify API rate limits in Python without crashing the script?","\nParse the ",[66,2016,818],{}," header from ",[66,2019,358],{}," responses and implement ",[66,2022,2023],{},"time.sleep()"," with the exact value. Shopify enforces a leaky-bucket algorithm (40 calls\u002Fsecond). For high-volume stores, switch to ",[66,2026,2027],{},"aiohttp",[66,2029,2030],{},"httpx"," to yield control back to the event loop during backoff periods.",[14,2033,2034,2037,2038,2041],{},[26,2035,2036],{},"Can I use webhooks instead of polling for real-time order sync?","\nYes. Configure an ",[66,2039,2040],{},"orders\u002Fcreate"," webhook pointing to a lightweight FastAPI or Flask endpoint. This eliminates polling overhead and reduces API quota consumption, but requires a publicly accessible server or serverless function to securely receive and verify HMAC-signed POST payloads.",[14,2043,2044,2047,2048,2051,2052,2054,2055,2058],{},[26,2045,2046],{},"Why is my Google Sheets API returning 403 Forbidden despite valid credentials?","\nThe service account email (",[66,2049,2050],{},"@project-id.iam.gserviceaccount.com",") must be explicitly shared as an ",[66,2053,96],{}," on the target Google Sheet. Additionally, verify that the Google Sheets API is enabled in the GCP console and that your credentials JSON includes the ",[66,2056,2057],{},"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fspreadsheets"," scope.",[14,2060,2061,2064,2065,2068,2069,2072,2073,2076],{},[26,2062,2063],{},"How do I prevent duplicate order entries when the script runs multiple times?","\nMaintain a local JSON state file or SQLite database tracking processed ",[66,2066,2067],{},"order_id"," values. Before appending, filter the fetched Shopify payload against this list using a Python ",[66,2070,2071],{},"set"," for O(1) lookups. Alternatively, use ",[66,2074,2075],{},"values().batchGet()"," to fetch existing IDs from the sheet and perform a set difference operation.",[2078,2079,2080],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":114,"searchDepth":127,"depth":127,"links":2082},[2083,2084,2085,2086,2087,2088,2089],{"id":51,"depth":127,"text":52},{"id":153,"depth":127,"text":154},{"id":824,"depth":127,"text":825},{"id":1234,"depth":127,"text":1235},{"id":1899,"depth":127,"text":1900},{"id":1950,"depth":127,"text":1951},{"id":2007,"depth":127,"text":2008},"Stop paying monthly SaaS fees for basic data routing. This guide delivers a direct, production-ready Python workflow to fetch Shopify orders and append them to Google Sheets using official REST APIs. By building a custom Automating Side-Hustle Operations with APIs pipeline, you bypass Zapier\u002FMake overhead while gaining full control over data transformation, error handling, and execution frequency.","md",{},"\u002Fautomating-side-hustle-operations-with-apis\u002Fautomating-social-media-posting\u002Fsync-shopify-orders-to-google-sheets-via-api",{"title":5,"description":2090},"automating-side-hustle-operations-with-apis\u002Fautomating-social-media-posting\u002Fsync-shopify-orders-to-google-sheets-via-api\u002Findex","dtWicq1ILX0YFvgYOKfKxI6G6ZgkJikyv2t2lJ94n2c",{"@context":2098,"@type":2099,"mainEntity":2100},"https:\u002F\u002Fschema.org","FAQPage",[2101,2106,2109,2112],{"@type":2102,"name":2013,"acceptedAnswer":2103},"Question",{"@type":2104,"text":2105},"Answer","Parse the Retry-After header from 429 responses and implement time.sleep() with the exact value. Shopify enforces a leaky-bucket algorithm (40 calls\u002Fsecond). For high-volume stores, switch to aiohttp or httpx to yield control back to the event loop during backoff periods.",{"@type":2102,"name":2036,"acceptedAnswer":2107},{"@type":2104,"text":2108},"Yes. Configure an orders\u002Fcreate webhook pointing to a lightweight FastAPI or Flask endpoint. This eliminates polling overhead and reduces API quota consumption, but requires a publicly accessible server or serverless function to securely receive and verify HMAC-signed POST payloads.",{"@type":2102,"name":2046,"acceptedAnswer":2110},{"@type":2104,"text":2111},"The service account email (@project-id.iam.gserviceaccount.com) must be explicitly shared as an Editor on the target Google Sheet. Additionally, verify that the Google Sheets API is enabled in the GCP console and that your credentials JSON includes the https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fspreadsheets scope.",{"@type":2102,"name":2063,"acceptedAnswer":2113},{"@type":2104,"text":2114},"Maintain a local JSON state file or SQLite database tracking processed order_id values. Before appending, filter the fetched Shopify payload against this list using a Python set for O(1) lookups. Alternatively, use values().batchGet() to fetch existing IDs from the sheet and perform a set difference operation.",1778017886164]