Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Microsoft Excel

Integrating Excel VBA with REST APIs: Fetch, Parse, and Automate Live Data Workflows

Learn how to build production-grade REST API integrations directly in Excel VBA — covering authenticated HTTP requests, JSON parsing, pagination, retry logic, and scheduled automation. This is the deep-dive lesson that gets you from manual copy-paste to fully automated live data workflows.

🔥 Expert26 min readAug 4, 2026Updated Aug 29, 2026
Integrating Excel VBA with REST APIs: Fetch, Parse, and Automate Live Data Workflows
On this page
  • Introduction
  • Prerequisites
  • Understanding the HTTP Stack Available to VBA
  • Building Your First API Request
  • Parsing JSON in Native VBA: The Ugly Truth and the Right Solutions
  • The ScriptControl Approach (32-bit Excel Only)
  • Using VBA-JSON: The Professional Choice
  • Writing Rates to the Worksheet
  • Authentication: Bearer Tokens, API Keys, and OAuth Patterns
  • API Key in Header (Most Common)
  • Storing API Keys Safely in VBA
POST Requests with JSON Body
  • Handling Pagination: Real APIs Return More Data Than Fits in One Response
  • Building a Reusable API Client Module
  • Automating Refresh: Scheduled and Event-Driven Updates
  • Application.OnTime for Scheduled Refreshes
  • Workbook Event-Driven Refresh
  • Building a Complete Real-World Example: GitHub Repository Dashboard
  • Hands-On Exercise
  • Common Mistakes & Troubleshooting
  • Summary & Next Steps
  • Integrating Excel VBA with REST APIs: Fetch, Parse, and Automate Live Data Workflows

    Introduction

    You've got a dashboard that your VP checks every Monday morning. It pulls currency exchange rates, inventory levels from your ERP system, or maybe weather data that feeds a logistics model. Right now, someone — probably you — manually downloads a CSV, pastes it in, reformats the headers, and runs a macro to refresh the pivot tables. It takes 45 minutes, it's error-prone, and it makes you feel like a human ETL pipeline.

    REST APIs exist to solve exactly this problem. Nearly every modern data system — financial data providers, CRMs, logistics platforms, government databases, and cloud services — exposes a REST API that lets you fetch structured, live data on demand. And Excel VBA, despite its age, has everything you need to make HTTP requests, parse JSON responses, and wire up fully automated workflows directly inside the workbooks your stakeholders already live in. This isn't a workaround; it's a legitimate integration pattern used in enterprise environments every day.

    By the end of this lesson, you'll be able to build production-grade VBA routines that authenticate with REST APIs, handle paginated responses, parse complex JSON structures without third-party libraries, manage errors gracefully, and schedule automated refreshes — all from inside Excel. We're going to build this up from the ground up, touching every rough edge along the way.

    What you'll learn:

    • How to make authenticated HTTP GET and POST requests from VBA using MSXML2.XMLHTTP60 and WinHttp.WinHttpRequest.5.1
    • How to parse JSON responses in native VBA without external dependencies, and when to use the ScriptControl or JsonConverter library approach instead
    • How to handle pagination, rate limiting, and multi-call workflows for real API endpoints
    • How to structure reusable, maintainable VBA API client modules with proper error handling
    • How to automate refresh cycles using Application.OnTime and workbook events

    Prerequisites

    This lesson assumes you're comfortable with VBA — you write subroutines and functions regularly, you understand object variables, and you're not intimidated by the VBA IDE. You should know what a REST API is conceptually: HTTP verbs, endpoints, request headers, and JSON responses. You don't need to be a web developer, but you should have seen a JSON payload before and understand what a status code like 200 or 401 means. If you've used Power Query to pull API data, you already have the right mental model — we're just dropping one level lower for more control.


    Understanding the HTTP Stack Available to VBA

    Before writing a single line of code, you need to understand your options, because the wrong choice here causes subtle, maddening bugs that only appear in certain environments.

    VBA on Windows has access to two primary COM objects for making HTTP requests:

    MSXML2.XMLHTTP60 — Part of the Microsoft XML library, this object runs on the same thread as Excel. It's synchronous by default, meaning Excel freezes while the request is in flight. It respects the user's Internet Explorer proxy settings, which is enormously important in corporate environments where all traffic must route through a proxy server.

    WinHttp.WinHttpRequest.5.1 — Part of the Windows HTTP Services library, this is a lower-level object that does not automatically inherit IE proxy settings. It's generally faster and more reliable for server-to-server type calls, but in a corporate network behind a proxy, you'll need to configure the proxy manually or your requests will silently fail.

    There's also MSXML2.ServerXMLHTTP60, which behaves like WinHttp in that it bypasses IE proxy settings. This is sometimes the right choice in automated, unattended scenarios.

    Here's how to choose:

    Scenario Recommended Object
    Interactive workbook, corporate network MSXML2.XMLHTTP60
    Automated/scheduled, no proxy WinHttp.WinHttpRequest.5.1
    Automated, needs proxy config MSXML2.ServerXMLHTTP60 with manual proxy
    Need async behavior MSXML2.XMLHTTP60 with OnReadyStateChange

    For this lesson, we'll use MSXML2.XMLHTTP60 for interactive examples and show you how to swap in WinHttp where it matters.

    Late Binding vs. Early Binding: You can declare these objects using early binding (Dim http As MSXML2.XMLHTTP60) after adding the reference via Tools → References, or late binding (Dim http As Object with CreateObject("MSXML2.XMLHTTP60")). Early binding gives you IntelliSense and slightly better performance. Late binding requires no reference and is more portable across machines. In production, use early binding during development and late binding for deployment — or just commit to early binding and document the required reference.


    Building Your First API Request

    Let's work with a real, publicly accessible API: the Open Exchange Rates API (free tier available) or, for zero-friction testing, https://api.exchangerate-api.com/v4/latest/USD, which requires no authentication. We'll also use https://jsonplaceholder.typicode.com for structured test payloads.

    Here is the foundational pattern for a GET request:

    Public Function MakeGetRequest(ByVal url As String) As String
        Dim http As Object
        Dim response As String
        
        Set http = CreateObject("MSXML2.XMLHTTP60")
        
        On Error GoTo HttpError
        
        http.Open "GET", url, False  ' False = synchronous
        http.setRequestHeader "Content-Type", "application/json"
        http.setRequestHeader "Accept", "application/json"
        http.setRequestHeader "User-Agent", "ExcelVBA/1.0"
        
        http.Send
        
        If http.Status = 200 Then
            response = http.responseText
        Else
            Err.Raise vbObjectError + 1001, "MakeGetRequest", _
                "HTTP Error " & http.Status & ": " & http.statusText
        End If
        
        MakeGetRequest = response
        Set http = Nothing
        Exit Function
    
    HttpError:
        Set http = Nothing
        MakeGetRequest = ""
        Debug.Print "Error in MakeGetRequest: " & Err.Description
        Err.Raise Err.Number, Err.Source, Err.Description
    End Function
    

    Notice a few things. We're not returning the raw http object — we're returning the response body as a string. This is intentional: it separates the transport layer from the parsing layer, which you'll appreciate when you're debugging at 10 PM. We also raise an error for non-200 status codes rather than silently returning an empty string. Swallowing HTTP errors is one of the most common mistakes in VBA API work, and it makes failures nearly impossible to diagnose.

    The User-Agent header deserves a mention. Some APIs reject requests without a User-Agent or with the default MSXML agent string. Setting a descriptive one is good practice and occasionally necessary.

    Let's call this and see it work:

    Public Sub TestExchangeRates()
        Dim jsonResponse As String
        Dim url As String
        
        url = "https://api.exchangerate-api.com/v4/latest/USD"
        jsonResponse = MakeGetRequest(url)
        
        Debug.Print Left(jsonResponse, 500)  ' Print first 500 chars to Immediate Window
    End Sub
    

    Run this and you should see something like:

    {"provider":"https://www.exchangerate-api.com","WARNING_UPGRADE_TO_V6":"...","terms":"...","base":"USD","date":"2024-11-15","time_last_updated":1731628800,"rates":{"AED":3.6725,"AFN":67.65,"ALL":92.67,"AMD":386.85,"ANG":1.79,...}}
    

    You've made your first live API call from Excel VBA. Now the real work begins — turning that string into usable data.


    Parsing JSON in Native VBA: The Ugly Truth and the Right Solutions

    This is where most VBA API tutorials fall apart. JSON parsing in native VBA is genuinely painful, and understanding why helps you make better decisions about your approach.

    VBA has no built-in JSON parser. Your options are:

    1. Manual string parsing — Fragile, unmaintainable, breaks on edge cases. Avoid for anything beyond the simplest key-value pairs.
    2. ScriptControl (JScript engine) — Use the built-in JavaScript engine to evaluate JSON. Elegant but limited to 32-bit Excel and deprecated in some environments.
    3. VBA-JSON (JsonConverter by Tim Hall) — Open source, single .bas module import, handles complex nested structures. This is the pragmatic choice for most professional use.
    4. Dictionary + custom parser — Viable for specific known structures, teaches you the internals.

    The ScriptControl Approach (32-bit Excel Only)

    Public Function ParseJsonWithScript(ByVal jsonString As String) As Object
        Dim sc As Object
        Set sc = CreateObject("ScriptControl")
        sc.Language = "JScript"
        
        sc.Eval "var data = " & jsonString
        Set ParseJsonWithScript = sc.CodeObject
    End Function
    

    This works beautifully in 32-bit Office but throws "ActiveX component can't create object" in 64-bit Excel. Since most enterprise Excel deployments are now 64-bit, this approach has a shrinking window of applicability. Don't build production systems on it.

    Using VBA-JSON: The Professional Choice

    Download the single file JsonConverter.bas from Tim Hall's GitHub repository (github.com/VBA-tools/VBA-JSON). In the VBA IDE, go to File → Import File and select the .bas file. Also add a reference to "Microsoft Scripting Runtime" (Tools → References) for the Dictionary object support.

    Once imported, you parse JSON like this:

    Public Sub ParseExchangeRates()
        Dim jsonString As String
        Dim parsed As Object
        Dim rates As Object
        Dim currencyCode As Variant
        
        jsonString = MakeGetRequest("https://api.exchangerate-api.com/v4/latest/USD")
        
        ' JsonConverter.ParseJson returns a Dictionary or Collection
        Set parsed = JsonConverter.ParseJson(jsonString)
        
        ' Access top-level keys like a Dictionary
        Debug.Print "Base currency: " & parsed("base")
        Debug.Print "Last updated: " & parsed("date")
        
        ' The "rates" key contains a nested Dictionary
        Set rates = parsed("rates")
        
        ' Iterate all currency codes
        For Each currencyCode In rates.Keys
            Debug.Print currencyCode & ": " & rates(currencyCode)
        Next currencyCode
    End Sub
    

    JSON objects become VBA Dictionary objects. JSON arrays become VBA Collection objects. Nested structures work as you'd expect — you just chain the key lookups. This is clean, readable, and handles the full JSON spec including Unicode escape sequences, which manual parsing gets wrong almost every time.

    Writing Rates to the Worksheet

    Let's make this actually useful:

    Public Sub RefreshExchangeRates()
        Dim ws As Worksheet
        Dim jsonString As String
        Dim parsed As Object
        Dim rates As Object
        Dim currencyCode As Variant
        Dim targetCurrencies As Variant
        Dim i As Integer
        
        ' Define which currencies we care about
        targetCurrencies = Array("EUR", "GBP", "JPY", "CAD", "AUD", "CHF", "CNY", "INR")
        
        Set ws = ThisWorkbook.Sheets("Exchange Rates")
        
        ' Clear old data (but preserve headers)
        ws.Range("A2:B100").ClearContents
        
        jsonString = MakeGetRequest("https://api.exchangerate-api.com/v4/latest/USD")
        
        If jsonString = "" Then
            MsgBox "Failed to fetch exchange rates. Check your connection.", vbCritical
            Exit Sub
        End If
        
        Set parsed = JsonConverter.ParseJson(jsonString)
        Set rates = parsed("rates")
        
        ' Write headers
        ws.Cells(1, 1).Value = "Currency"
        ws.Cells(1, 2).Value = "Rate (vs USD)"
        ws.Cells(1, 3).Value = "Last Updated"
        
        i = 2
        For Each currencyCode In targetCurrencies
            If rates.Exists(currencyCode) Then
                ws.Cells(i, 1).Value = currencyCode
                ws.Cells(i, 2).Value = rates(currencyCode)
                ws.Cells(i, 3).Value = parsed("date")
                i = i + 1
            End If
        Next currencyCode
        
        ws.Columns("A:C").AutoFit
        
        Debug.Print "Rates refreshed at " & Now()
    End Sub
    

    Important

    Notice we check rates.Exists(currencyCode) before accessing the value. Trying to access a key that doesn't exist in a VBA Dictionary raises error 457. Get in the habit of always checking .Exists() on dynamic dictionary lookups.


    Authentication: Bearer Tokens, API Keys, and OAuth Patterns

    Most real-world APIs require authentication. Let's cover the patterns you'll actually encounter.

    API Key in Header (Most Common)

    Many APIs — Alpha Vantage, OpenWeatherMap, Airtable, and hundreds of others — authenticate via a key in a request header:

    Public Function MakeAuthenticatedRequest(ByVal url As String, _
                                              ByVal apiKey As String) As String
        Dim http As Object
        Set http = CreateObject("MSXML2.XMLHTTP60")
        
        http.Open "GET", url, False
        http.setRequestHeader "Content-Type", "application/json"
        http.setRequestHeader "Accept", "application/json"
        http.setRequestHeader "Authorization", "Bearer " & apiKey
        ' Some APIs use a custom header name:
        ' http.setRequestHeader "X-API-Key", apiKey
        ' http.setRequestHeader "apikey", apiKey
        
        http.Send
        
        If http.Status = 200 Then
            MakeAuthenticatedRequest = http.responseText
        Else
            Err.Raise vbObjectError + 1001, "MakeAuthenticatedRequest", _
                "HTTP " & http.Status & " - " & http.responseText
        End If
        
        Set http = Nothing
    End Function
    

    Storing API Keys Safely in VBA

    Never hardcode API keys in your VBA source code. If the workbook is shared, emailed, or stored in a shared drive, your credentials are exposed. Here are three progressively better approaches:

    Approach 1: Named Range as Config Store the key in a hidden, password-protected sheet:

    Function GetApiKey(keyName As String) As String
        Dim configSheet As Worksheet
        Set configSheet = ThisWorkbook.Sheets("_Config")  ' Hidden sheet
        
        Dim keyRange As Range
        Set keyRange = configSheet.Columns(1).Find(keyName, LookAt:=xlWhole)
        
        If Not keyRange Is Nothing Then
            GetApiKey = keyRange.Offset(0, 1).Value
        Else
            Err.Raise vbObjectError + 2001, "GetApiKey", "Key not found: " & keyName
        End If
    End Function
    

    Approach 2: Windows Registry

    Function GetApiKeyFromRegistry(keyName As String) As String
        Dim wsh As Object
        Set wsh = CreateObject("WScript.Shell")
        On Error Resume Next
        GetApiKeyFromRegistry = wsh.RegRead("HKCU\Software\WickedSmartData\" & keyName)
        On Error GoTo 0
        Set wsh = Nothing
    End Function
    

    Approach 3: Environment Variables (best for shared/automated environments)

    Function GetApiKeyFromEnv(envVarName As String) As String
        GetApiKeyFromEnv = Environ(envVarName)
    End Function
    

    Set environment variables at the OS level. They're not visible in the workbook file at all.

    POST Requests with JSON Body

    For APIs that require POST — creating records, submitting data, triggering jobs — you need to send a JSON body:

    Public Function MakePostRequest(ByVal url As String, _
                                     ByVal jsonBody As String, _
                                     ByVal apiKey As String) As String
        Dim http As Object
        Set http = CreateObject("MSXML2.XMLHTTP60")
        
        http.Open "POST", url, False
        http.setRequestHeader "Content-Type", "application/json"
        http.setRequestHeader "Accept", "application/json"
        http.setRequestHeader "Authorization", "Bearer " & apiKey
        
        ' Send converts String to bytes; encoding matters here
        http.Send jsonBody
        
        ' Accept 200 and 201 (Created) as success
        If http.Status = 200 Or http.Status = 201 Then
            MakePostRequest = http.responseText
        Else
            Err.Raise vbObjectError + 1001, "MakePostRequest", _
                "HTTP " & http.Status & ": " & http.responseText
        End If
        
        Set http = Nothing
    End Function
    

    Building the JSON body as a string manually works for simple structures:

    Dim payload As String
    payload = "{""symbol"": ""AAPL"", ""quantity"": 100, ""order_type"": ""market""}"
    

    For complex nested structures, use VBA-JSON's ConvertToJson function:

    Dim orderDict As Object
    Set orderDict = CreateObject("Scripting.Dictionary")
    orderDict.Add "symbol", "AAPL"
    orderDict.Add "quantity", 100
    orderDict.Add "order_type", "market"
    orderDict.Add "timestamp", Format(Now(), "yyyy-mm-ddThh:mm:ss") & "Z"
    
    Dim payload As String
    payload = JsonConverter.ConvertToJson(orderDict)
    ' payload = {"symbol":"AAPL","quantity":100,"order_type":"market","timestamp":"2024-11-15T14:30:00Z"}
    

    Handling Pagination: Real APIs Return More Data Than Fits in One Response

    This is where VBA API integrations most commonly break down in production. You fetch 100 records and think you have everything, but the API quietly returned only the first page. A week later someone notices the data is wrong.

    Most REST APIs signal pagination through one of three mechanisms:

    1. Offset/Limit Parameters (most common)

    GET /api/v1/transactions?limit=100&offset=0
    GET /api/v1/transactions?limit=100&offset=100
    GET /api/v1/transactions?limit=100&offset=200
    

    2. Page Number Parameters

    GET /api/v1/transactions?page=1&per_page=100
    GET /api/v1/transactions?page=2&per_page=100
    

    3. Cursor-Based Pagination (GitHub, Twitter/X, modern APIs) The response includes a next_cursor or next_page_token that you pass in the next request.

    Here's a robust paginated fetch that handles offset/limit pagination and collects all records:

    Public Function FetchAllTransactions(ByVal baseUrl As String, _
                                          ByVal apiKey As String) As Collection
        Dim allRecords As Collection
        Set allRecords = New Collection
        
        Dim pageSize As Integer
        Dim offset As Long
        Dim hasMore As Boolean
        Dim jsonString As String
        Dim parsed As Object
        Dim pageData As Object
        Dim record As Object
        Dim totalFetched As Long
        Dim requestCount As Integer
        
        pageSize = 100
        offset = 0
        hasMore = True
        totalFetched = 0
        requestCount = 0
        
        Const MAX_REQUESTS As Integer = 50  ' Safety ceiling: 5,000 records max
        
        Do While hasMore And requestCount < MAX_REQUESTS
            Dim pageUrl As String
            pageUrl = baseUrl & "?limit=" & pageSize & "&offset=" & offset
            
            jsonString = MakeAuthenticatedRequest(pageUrl, apiKey)
            
            If jsonString = "" Then
                Err.Raise vbObjectError + 3001, "FetchAllTransactions", _
                    "Empty response at offset " & offset
            End If
            
            Set parsed = JsonConverter.ParseJson(jsonString)
            
            ' Most APIs wrap results in a key like "data", "results", or "items"
            Set pageData = parsed("data")
            
            If pageData.Count = 0 Then
                hasMore = False
            Else
                Dim item As Variant
                For Each item In pageData
                    allRecords.Add item
                Next item
                
                totalFetched = totalFetched + pageData.Count
                offset = offset + pageSize
                requestCount = requestCount + 1
                
                ' Check if this page was smaller than the page size (last page)
                If pageData.Count < pageSize Then
                    hasMore = False
                End If
                
                ' Respect rate limits: pause between requests
                ' Many APIs allow 1 req/second on free tiers
                Application.Wait (Now + TimeValue("0:00:01"))
            End If
            
            Debug.Print "Fetched " & totalFetched & " records so far..."
            DoEvents  ' Keep Excel responsive during long fetches
        Loop
        
        Debug.Print "Total records fetched: " & totalFetched & " in " & requestCount & " requests"
        Set FetchAllTransactions = allRecords
    End Function
    

    The Application.Wait call between requests is critical. Rate limiting is real — most free API tiers cap at 60 requests per minute or fewer. Hammering the API without delays will get your key suspended. The DoEvents call keeps Excel's UI thread alive so the user can still cancel if needed.

    A word on cursor-based pagination: For cursor-based APIs, the loop structure is similar, but instead of incrementing an offset, you extract the next cursor from the response and stop when the cursor is null or missing:

    Dim nextCursor As String
    nextCursor = ""
    ' In the loop:
    If parsed.Exists("next_cursor") And parsed("next_cursor") <> "" Then
        nextCursor = parsed("next_cursor")
        pageUrl = baseUrl & "?cursor=" & nextCursor
    Else
        hasMore = False
    End If
    

    Building a Reusable API Client Module

    Ad-hoc spaghetti code that makes API calls inline in your business logic routines is a maintenance nightmare. As your integration grows, you want a dedicated module that encapsulates all the HTTP mechanics. Here's a production-grade structure:

    ' =============================================================================
    ' Module: modApiClient
    ' Purpose: Centralized HTTP client for REST API operations
    ' Author: [Your Name]
    ' Dependencies: VBA-JSON (JsonConverter), Microsoft Scripting Runtime
    ' =============================================================================
    Option Explicit
    
    ' --- Configuration Constants ---
    Private Const BASE_URL As String = "https://api.yourservice.com/v2"
    Private Const REQUEST_TIMEOUT_MS As Long = 30000  ' 30 seconds
    Private Const RETRY_ATTEMPTS As Integer = 3
    Private Const RETRY_DELAY_SECONDS As Integer = 2
    
    ' --- Core HTTP Engine ---
    Private Function ExecuteRequest(ByVal method As String, _
                                     ByVal endpoint As String, _
                                     Optional ByVal body As String = "", _
                                     Optional ByVal apiKey As String = "") As String
        Dim http As Object
        Dim attempt As Integer
        Dim lastError As String
        
        Set http = CreateObject("MSXML2.XMLHTTP60")
        
        ' Set timeout (WinHttp uses SetTimeouts; XMLHTTP uses a different approach)
        ' For XMLHTTP, we manage timeout via Application.Wait externally
        
        For attempt = 1 To RETRY_ATTEMPTS
            On Error Resume Next
            
            http.Open method, BASE_URL & endpoint, False
            http.setRequestHeader "Content-Type", "application/json; charset=utf-8"
            http.setRequestHeader "Accept", "application/json"
            http.setRequestHeader "User-Agent", "ExcelVBA-Client/2.0"
            
            If apiKey <> "" Then
                http.setRequestHeader "Authorization", "Bearer " & apiKey
            End If
            
            If method = "POST" Or method = "PUT" Or method = "PATCH" Then
                http.Send body
            Else
                http.Send
            End If
            
            Dim sendError As Long
            sendError = Err.Number
            On Error GoTo 0
            
            If sendError <> 0 Then
                ' Network-level error (no connection, DNS failure, etc.)
                lastError = "Network error on attempt " & attempt
                If attempt < RETRY_ATTEMPTS Then
                    Application.Wait (Now + TimeValue("0:00:0" & RETRY_DELAY_SECONDS))
                End If
            ElseIf http.Status >= 500 Then
                ' Server error — retry
                lastError = "Server error " & http.Status & " on attempt " & attempt
                If attempt < RETRY_ATTEMPTS Then
                    Application.Wait (Now + TimeValue("0:00:0" & RETRY_DELAY_SECONDS))
                End If
            ElseIf http.Status = 429 Then
                ' Rate limited — back off longer
                lastError = "Rate limited on attempt " & attempt
                Dim retryAfter As Integer
                retryAfter = 10  ' Default: wait 10 seconds
                On Error Resume Next
                retryAfter = CInt(http.getResponseHeader("Retry-After"))
                On Error GoTo 0
                Application.Wait (Now + TimeValue("0:00:" & Format(retryAfter, "00")))
            ElseIf http.Status >= 200 And http.Status < 300 Then
                ' Success
                ExecuteRequest = http.responseText
                Set http = Nothing
                Exit Function
            Else
                ' Client error (400, 401, 403, 404) — don't retry
                Err.Raise vbObjectError + http.Status, "ExecuteRequest", _
                    "HTTP " & http.Status & ": " & http.responseText
                Set http = Nothing
                Exit Function
            End If
        Next attempt
        
        ' All retries exhausted
        Set http = Nothing
        Err.Raise vbObjectError + 3000, "ExecuteRequest", _
            "All " & RETRY_ATTEMPTS & " attempts failed. Last: " & lastError
    End Function
    
    ' --- Public API Methods ---
    Public Function ApiGet(ByVal endpoint As String, _
                            Optional ByVal apiKey As String = "") As Object
        Dim response As String
        response = ExecuteRequest("GET", endpoint, , apiKey)
        Set ApiGet = JsonConverter.ParseJson(response)
    End Function
    
    Public Function ApiPost(ByVal endpoint As String, _
                             ByVal payload As Object, _
                             Optional ByVal apiKey As String = "") As Object
        Dim body As String
        body = JsonConverter.ConvertToJson(payload)
        Dim response As String
        response = ExecuteRequest("POST", endpoint, body, apiKey)
        Set ApiPost = JsonConverter.ParseJson(response)
    End Function
    
    Public Function ApiDelete(ByVal endpoint As String, _
                               Optional ByVal apiKey As String = "") As Boolean
        Dim response As String
        response = ExecuteRequest("DELETE", endpoint, , apiKey)
        ApiDelete = True  ' If we got here without error, it succeeded
    End Function
    

    This module gives you automatic retry with exponential-ish backoff, rate limit handling via Retry-After headers, clean separation of transport and parsing, and public methods that return already-parsed objects. Your business logic routines call ApiGet("/transactions") and get a Dictionary back — they never touch HTTP.


    Automating Refresh: Scheduled and Event-Driven Updates

    You've got the data flow working. Now let's make it run automatically.

    Application.OnTime for Scheduled Refreshes

    Application.OnTime schedules a macro to run at a specific time. Combine it with a self-rescheduling pattern to create a polling loop:

    ' In a standard module:
    Public refreshJobTime As Date  ' Track the scheduled time so we can cancel it
    
    Public Sub ScheduleRefresh()
        Const REFRESH_INTERVAL_MINUTES As Integer = 15
        
        refreshJobTime = Now + TimeValue("0:" & REFRESH_INTERVAL_MINUTES & ":00")
        Application.OnTime refreshJobTime, "RunRefreshAndReschedule"
        
        Debug.Print "Next refresh scheduled for: " & refreshJobTime
    End Sub
    
    Public Sub RunRefreshAndReschedule()
        On Error GoTo RefreshError
        
        ' Run the actual data refresh
        Call RefreshExchangeRates
        Call RefreshInventoryData
        
        ' Update status cell
        ThisWorkbook.Sheets("Dashboard").Range("B1").Value = _
            "Last refreshed: " & Format(Now(), "yyyy-mm-dd hh:mm:ss")
        
        ' Reschedule
        Call ScheduleRefresh
        Exit Sub
    
    RefreshError:
        ' Log error but still reschedule — don't let one failure stop the automation
        Debug.Print "Refresh error at " & Now() & ": " & Err.Description
        
        ' Write error to log sheet
        LogError "Scheduled Refresh", Err.Description
        
        ' Reschedule anyway
        Call ScheduleRefresh
    End Sub
    
    Public Sub CancelRefresh()
        On Error Resume Next  ' In case the job isn't scheduled
        Application.OnTime refreshJobTime, "RunRefreshAndReschedule", , False
        On Error GoTo 0
        Debug.Print "Refresh cancelled."
    End Sub
    

    Wire ScheduleRefresh to the Workbook_Open event and CancelRefresh to Workbook_BeforeClose:

    ' In ThisWorkbook:
    Private Sub Workbook_Open()
        Call ScheduleRefresh
    End Sub
    
    Private Sub Workbook_BeforeClose(Cancel As Boolean)
        Call CancelRefresh
    End Sub
    

    Critical caveat

    Application.OnTime only fires while Excel is open and the workbook is active. If the user closes the workbook or Excel crashes, the schedule is gone. For truly unattended scheduling (overnight batch jobs), you need Windows Task Scheduler calling a VBScript or PowerShell that opens the workbook, runs the macro, and closes it. Application.OnTime is for "refresh every 15 minutes while someone is working with this file" — not for "run at 3 AM."

    Workbook Event-Driven Refresh

    For dashboards where users navigate between sheets, you can trigger a refresh when they activate the data sheet:

    ' In the Sheet module for your dashboard sheet:
    Private Sub Worksheet_Activate()
        ' Only refresh if data is stale (older than 5 minutes)
        Dim lastRefresh As Date
        Dim lastRefreshCell As Range
        
        Set lastRefreshCell = Me.Range("B1")
        
        If lastRefreshCell.Value = "" Then
            Call RefreshExchangeRates
            Exit Sub
        End If
        
        On Error Resume Next
        lastRefresh = CDate(Replace(lastRefreshCell.Value, "Last refreshed: ", ""))
        On Error GoTo 0
        
        If lastRefresh = 0 Or DateDiff("n", lastRefresh, Now()) > 5 Then
            Call RefreshExchangeRates
        End If
    End Sub
    

    Building a Complete Real-World Example: GitHub Repository Dashboard

    Let's put it all together. We'll build a dashboard that pulls data from the GitHub API — a real, freely accessible API with straightforward authentication — showing you repository stats, open issues, and recent commits for a list of repos you define.

    ' ============================================================
    ' Complete GitHub Dashboard Refresh
    ' Requires: VBA-JSON imported, GitHub Personal Access Token
    ' ============================================================
    Public Sub RefreshGitHubDashboard()
        Dim ws As Worksheet
        Dim apiKey As String
        Dim repos As Variant
        Dim repoName As Variant
        Dim row As Integer
        Dim parsed As Object
        Dim url As String
        
        ' Get token from hidden config sheet
        apiKey = GetApiKey("GITHUB_TOKEN")
        
        ' Define repos to monitor (owner/repo format)
        repos = Array( _
            "pandas-dev/pandas", _
            "numpy/numpy", _
            "scikit-learn/scikit-learn", _
            "microsoft/vscode" _
        )
        
        Set ws = ThisWorkbook.Sheets("GitHub Dashboard")
        
        ' Write headers
        With ws
            .Range("A1:G1").Value = Array( _
                "Repository", "Stars", "Forks", "Open Issues", _
                "Last Push", "Language", "Description")
            .Range("A1:G1").Font.Bold = True
            .Range("A2:G100").ClearContents
        End With
        
        row = 2
        For Each repoName In repos
            url = "https://api.github.com/repos/" & repoName
            
            On Error GoTo RepoError
            
            Dim jsonStr As String
            jsonStr = MakeAuthenticatedRequest(url, apiKey)
            Set parsed = JsonConverter.ParseJson(jsonStr)
            
            With ws
                .Cells(row, 1).Value = repoName
                .Cells(row, 2).Value = parsed("stargazers_count")
                .Cells(row, 3).Value = parsed("forks_count")
                .Cells(row, 4).Value = parsed("open_issues_count")
                
                ' GitHub returns ISO 8601 dates like "2024-11-15T12:30:00Z"
                Dim pushDate As String
                pushDate = parsed("pushed_at")
                ' Convert to Excel date: strip the T and Z
                .Cells(row, 5).Value = CDate(Replace(Replace(pushDate, "T", " "), "Z", ""))
                .Cells(row, 5).NumberFormat = "yyyy-mm-dd hh:mm"
                
                ' Handle nullable fields
                If Not IsNull(parsed("language")) And parsed("language") <> "" Then
                    .Cells(row, 6).Value = parsed("language")
                Else
                    .Cells(row, 6).Value = "Multiple"
                End If
                
                .Cells(row, 7).Value = Left(parsed("description"), 100)
            End With
            
            row = row + 1
            
            ' GitHub API: 60 req/hour unauthenticated, 5000/hour authenticated
            ' Still good practice to be gentle
            Application.Wait (Now + TimeValue("0:00:01"))
            DoEvents
            
            GoTo NextRepo
            
    RepoError:
            ws.Cells(row, 1).Value = repoName
            ws.Cells(row, 2).Value = "ERROR: " & Err.Description
            row = row + 1
            Err.Clear
            Resume NextRepo
    
    NextRepo:
        Next repoName
        
        ws.Columns("A:G").AutoFit
        ws.Range("B2:D" & row - 1).NumberFormat = "#,##0"
        
        ' Sort by stars descending
        ws.Sort.SortFields.Clear
        ws.Sort.SortFields.Add Key:=ws.Range("B2:B" & row - 1), _
            Order:=xlDescending
        ws.Sort.SetRange ws.Range("A1:G" & row - 1)
        ws.Sort.Header = xlYes
        ws.Sort.Apply
        
        MsgBox "GitHub Dashboard refreshed! " & (row - 2) & " repos loaded.", vbInformation
    End Sub
    

    Notice how we handle nullable fields (the description can be null for some repos), parse ISO 8601 dates, handle per-row errors without stopping the whole process, and apply formatting after the data is written. These aren't edge cases — they're the normal texture of real API integration work.


    Hands-On Exercise

    Build an automated currency conversion dashboard using the following requirements:

    Setup: Create a workbook with two sheets: "Config" and "FX Dashboard."

    Part 1 — Data Fetch (30 minutes) Using https://api.exchangerate-api.com/v4/latest/USD (no auth required), write a FetchFXRates() function that:

    1. Makes the GET request using MSXML2.XMLHTTP60
    2. Parses the response with VBA-JSON
    3. Returns a Dictionary of all exchange rates

    Part 2 — Dashboard Population (20 minutes) Write PopulateFXDashboard() that:

    1. Reads a list of currency codes from column A of the "Config" sheet (put 10-15 currencies there yourself)
    2. Fetches live rates
    3. Writes each currency, its rate vs USD, and the inverse rate (USD per 1 unit of currency) to the "FX Dashboard" sheet
    4. Applies conditional formatting — green if rate improved vs yesterday's value in column D, red if it worsened

    Part 3 — Automation (20 minutes)

    1. Add a "Refresh" button on the dashboard sheet that calls your populate routine
    2. Wire Workbook_Open to call ScheduleRefresh for a 10-minute polling interval
    3. Store yesterday's rates in column D when you write today's rates, so tomorrow's run can compare

    Stretch goal: Add a POST call to https://jsonplaceholder.typicode.com/posts that sends a JSON summary of your highest and lowest rate, and display the response in a "Last Sync" cell on the dashboard.


    Common Mistakes & Troubleshooting

    "The remote server returned an error: (407) Proxy Authentication Required" You're on a corporate network with a proxy. Switch from MSXML2.XMLHTTP60 to WinHttp.WinHttpRequest.5.1 and configure the proxy:

    http.SetProxy 2, "proxy.yourcompany.com:8080"
    http.SetCredentials "domain\username", "password", 0
    

    Or stick with MSXML2.XMLHTTP60 — it respects IE proxy settings automatically, which usually means it works without any extra configuration.

    "Run-time error '91': Object variable or With block variable not set" when accessing parsed JSON The key you're trying to access doesn't exist. Use parsed.Exists("key") before accessing it, or wrap in On Error Resume Next. Also check: did the API return an error response (a JSON object with an "error" key) instead of the data you expected? Print http.Status and http.responseText before parsing to verify what actually came back.

    JSON parsing silently returns wrong values for numbers VBA's Dictionary stores everything as Variant. A number like 3.6725 might come back as a String in some edge cases depending on locale settings. Use CDbl() or CLng() explicitly when writing numeric API values to cells.

    Charset/encoding issues — garbled characters in responses API responses with non-ASCII characters (currency symbols, names with accents) can get mangled. Use http.responseBody (a byte array) instead of http.responseText, and decode it explicitly:

    Dim stream As Object
    Set stream = CreateObject("ADODB.Stream")
    stream.Open
    stream.Type = 1  ' Binary
    stream.Write http.responseBody
    stream.Position = 0
    stream.Type = 2  ' Text
    stream.Charset = "UTF-8"
    Dim properText As String
    properText = stream.ReadText
    stream.Close
    

    Application.OnTime macro not firing Check that macro security settings allow macros to run without prompts when the workbook opens. If the VBA project is unprotected and macros are enabled, OnTime should work. Also verify the macro name string matches exactly, including module name if needed: "modRefresh.RunRefreshAndReschedule".

    Rate limit errors appearing inconsistently You're making multiple calls in rapid succession from different subroutines. Centralize all HTTP calls through your modApiClient module and add a global request throttle using a module-level timestamp:

    Private lastRequestTime As Date
    Private Const MIN_REQUEST_INTERVAL_MS As Long = 1000
    
    Private Sub ThrottleRequest()
        Dim elapsed As Long
        elapsed = (Now - lastRequestTime) * 86400000  ' Convert to ms
        If elapsed < MIN_REQUEST_INTERVAL_MS Then
            Application.Wait (Now + (MIN_REQUEST_INTERVAL_MS - elapsed) / 86400000)
        End If
        lastRequestTime = Now
    End Sub
    

    Call ThrottleRequest at the start of ExecuteRequest.


    Summary & Next Steps

    You now have the complete toolkit for production REST API integration in Excel VBA. You understand the difference between HTTP client objects and when to use each one. You can make authenticated GET and POST requests, parse complex JSON responses, handle pagination correctly, manage rate limits gracefully, and wire up scheduled automation — all in a maintainable, modular structure.

    The patterns we've built here — the centralized API client module, the retry logic, the safe key storage, the pagination loop — are not VBA-specific ideas. They're software engineering fundamentals that happen to be implemented in VBA. That means the skills transfer directly to Python, JavaScript, or any other language you might use in the future.

    Where to go from here:

    • Webhooks and inbound data: Most of this lesson covered VBA as an API client (outbound calls). Explore using a local Flask server or Azure Functions as a webhook receiver that writes data to a shared location Excel can read.
    • Power Query as a complement: For scheduled, credential-managed, transformation-heavy API calls, Power Query's Web.Contents function is worth learning. Use VBA for interactive, event-driven, or write-back scenarios; use Power Query for batch read scenarios.
    • Error telemetry: Log all API errors (timestamps, endpoints, status codes, response bodies) to a dedicated log sheet or a database. When things break at 3 AM in an unattended automation, that log is your only forensic evidence.
    • OAuth 2.0 flows: APIs like Google Sheets, Salesforce, and Microsoft Graph require OAuth, which involves redirect URIs and token refresh flows. This is significantly more complex in VBA but doable using the Shell command to open the auth URL in a browser and a local HTTP listener to capture the callback token.
    • Unit testing your VBA: The Rubberduck VBA IDE extension brings unit testing to VBA, which lets you write tests for your JSON parsing and data transformation logic independent of live API calls.

    The spreadsheet your VP checks on Monday morning doesn't have to be a manual ritual. It can be a live, self-maintaining data product — and you now know exactly how to build it.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Advanced Excel & VBA

    Previous

    Building Excel Add-Ins with VBA: Package and Deploy Custom Tools Across Your Organization

    Next

    Mastering Excel's Name Manager: Define, Organize, and Use Named Ranges for Cleaner Formulas and VBA

    Related Insights

    Microsoft ExcelPractitioner

    Building a Custom Excel Task Pane with VBA and HTML: Design Interactive Side-Panel Interfaces for Professional Workbook Applications

    25 min
    Microsoft ExcelFoundation

    Understanding Excel's Calculation Engine: Volatile Functions, Dependency Chains, and Manual vs. Automatic Calculation Modes

    17 min
    Microsoft ExcelExpert

    Building a Custom Excel Ribbon with VBA and XML: Design, Deploy, and Control Context-Sensitive UI for Professional-Grade Workbooks

    26 min

    On this page

    • Introduction
    • Prerequisites
    • Understanding the HTTP Stack Available to VBA
    • Building Your First API Request
    • Parsing JSON in Native VBA: The Ugly Truth and the Right Solutions
    • The ScriptControl Approach (32-bit Excel Only)
    • Using VBA-JSON: The Professional Choice
    • Writing Rates to the Worksheet
    • Authentication: Bearer Tokens, API Keys, and OAuth Patterns
    • API Key in Header (Most Common)
    • Storing API Keys Safely in VBA
    • POST Requests with JSON Body
    • Handling Pagination: Real APIs Return More Data Than Fits in One Response
    • Building a Reusable API Client Module
    • Automating Refresh: Scheduled and Event-Driven Updates
    • Application.OnTime for Scheduled Refreshes
    • Workbook Event-Driven Refresh
    • Building a Complete Real-World Example: GitHub Repository Dashboard
    • Hands-On Exercise
    • Common Mistakes & Troubleshooting
    • Summary & Next Steps