Expert guide

Connect Microsoft Access to the OpenAI API with VBA

Summaries, classifications and drafts written straight into your Access tables

Short answer: add an AskAI function to a standard Access module that posts JSON to the OpenAI chat completions endpoint with MSXML2.ServerXMLHTTP and an API key read from an environment variable, then call it from a form button or a batch loop over a DAO recordset. Write results to dedicated AIResult and AIStatus fields, never over the source text, and log errors per record. For production use across a team, build it as an Access API integration with central key management.

Access databases are full of text that nobody has time to read: job notes, call logs, complaint descriptions, inspection comments, intake forms. AI models are good at reading text and returning something short and structured: a summary, a category, a priority, a draft reply. Connecting the two takes one VBA module and a few extra fields.

Step 1: Prepare the Table

Add fields to the table that holds the text, or better, create a child table so every AI call is kept.

FieldTypePurpose
AIResultLong TextThe model's answer
AIStatusShort TextPending, Processing, Done, Error
AIErrorShort Text (255)Error message if the call failed
AIModelShort TextModel name used, for auditing
AIProcessedOnDate/TimeWhen it ran
AIProcessedByShort TextWindows user who ran it

Step 2: The Reusable AskAI Function

Insert a new standard module named modAI. Set the API key as a Windows user environment variable named OPENAI_API_KEY and restart Access.

Option Compare Database
Option Explicit

Public Const AI_MODEL As String = "gpt-4.1-mini"   ' set to a current model
Private Const AI_URL As String = "https://api.openai.com/v1/chat/completions"

Public Function AskAI(ByVal instruction As String, ByVal inputText As String) As String
    Dim http As Object, body As String, key As String

    key = Environ("OPENAI_API_KEY")
    If Len(key) = 0 Then Err.Raise vbObjectError + 1, "AskAI", "OPENAI_API_KEY not set"

    body = "{""model"":""" & AI_MODEL & """,""temperature"":0.1," & _
           """messages"":[{""role"":""system"",""content"":""" & JsonEsc(instruction) & """}," & _
           "{""role"":""user"",""content"":""" & JsonEsc(inputText) & """}]}"

    Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    http.setTimeouts 5000, 5000, 30000, 90000
    http.Open "POST", AI_URL, False
    http.setRequestHeader "Content-Type", "application/json"
    http.setRequestHeader "Authorization", "Bearer " & key
    http.send body

    If http.Status <> 200 Then
        Err.Raise vbObjectError + http.Status, "AskAI", _
                  "HTTP " & http.Status & ": " & Left$(http.responseText, 200)
    End If
    AskAI = ReadContent(http.responseText)
End Function

Private Function JsonEsc(ByVal s As String) As String
    s = Replace(s, "\", "\\"): s = Replace(s, """", "\""")
    s = Replace(s, vbCrLf, "\n"): s = Replace(s, vbCr, "\n")
    s = Replace(s, vbLf, "\n"):   s = Replace(s, vbTab, "\t")
    JsonEsc = s
End Function

Private Function ReadContent(ByVal json As String) As String
    Dim p As Long, i As Long, c As String, out As String
    p = InStr(json, """content"":")
    If p = 0 Then Exit Function
    i = InStr(p + 10, json, """") + 1
    Do While i <= Len(json)
        c = Mid$(json, i, 1)
        If c = "\" Then
            Select Case Mid$(json, i + 1, 1)
                Case "n": out = out & vbCrLf
                Case "t": out = out & vbTab
                Case Else: out = out & Mid$(json, i + 1, 1)
            End Select
            i = i + 2
        ElseIf c = """" Then
            Exit Do
        Else
            out = out & c: i = i + 1
        End If
    Loop
    ReadContent = out
End Function

The response parser is deliberately minimal. If you ask the model for structured JSON with several fields, use a proper parser as shown in parsing JSON in Access VBA.

Step 3: Use It on a Form

A Summarise button on a job or case form gives users AI on demand for the current record.

Private Sub cmdSummarise_Click()
    On Error GoTo Fail
    If Len(Nz(Me.Notes, "")) < 20 Then
        MsgBox "Not enough notes to summarise.", vbInformation: Exit Sub
    End If
    DoCmd.Hourglass True
    Me.AIResult = AskAI( _
        "Summarise these job notes in 3 bullet points for a manager. " & _
        "Include any promised dates. Do not add facts.", Me.Notes)
    Me.AIStatus = "Done"
    Me.AIModel = AI_MODEL
    Me.AIProcessedOn = Now()
    Me.AIProcessedBy = Environ("USERNAME")
    Me.Dirty = False
Done:
    DoCmd.Hourglass False
    Exit Sub
Fail:
    DoCmd.Hourglass False
    MsgBox "AI summary failed: " & Err.Description, vbExclamation
End Sub

Step 4: Batch Processing Many Records

Public Sub ClassifyPendingTickets()
    Const INSTR_TEXT As String = _
        "Classify this support ticket. Reply with exactly one of: " & _
        "Billing, Technical, Delivery, Account, Complaint, Other."
    Dim db As DAO.Database, rs As DAO.Recordset, n As Long

    Set db = CurrentDb
    ' Claim rows first so another user's batch skips them
    db.Execute "UPDATE tblTickets SET AIStatus='Processing', AIProcessedBy='" & _
               Environ("USERNAME") & "' WHERE Nz(AIStatus,'Pending')='Pending'", dbFailOnError

    Set rs = db.OpenRecordset("SELECT * FROM tblTickets WHERE AIStatus='Processing' " & _
             "AND AIProcessedBy='" & Environ("USERNAME") & "'", dbOpenDynaset)

    Do While Not rs.EOF
        rs.Edit
        On Error Resume Next
        rs!AIResult = AskAI(INSTR_TEXT, Nz(rs!Description, ""))
        If Err.Number = 0 Then
            rs!AIStatus = "Done"
        Else
            rs!AIStatus = "Error": rs!AIError = Left$(Err.Description, 255)
        End If
        Err.Clear: On Error GoTo 0
        rs!AIModel = AI_MODEL: rs!AIProcessedOn = Now()
        rs.Update
        n = n + 1
        SysCmd acSysCmdSetStatus, "Classified " & n
        DoEvents
        rs.MoveNext
    Loop
    rs.Close
    SysCmd acSysCmdClearStatus
    MsgBox n & " tickets classified.", vbInformation
End Sub

Claiming rows with an UPDATE before processing stops two users classifying the same tickets and paying twice. Rows marked Error can be reset to Pending and retried.

Cost, Speed and Safety

  • Send only the field you need

    Not the whole record. Leave out names, phone numbers and account numbers unless the task needs them.

  • Constrain outputs

    A fixed list of categories can be enforced with a lookup table and a validation query afterwards.

  • Use a small model for classification

    Reserve larger models for long documents or complex drafting.

  • Do not ship keys to every desktop

    In a team, route calls through one service or scheduled process that holds the key.

  • Keep humans in the loop

    AI-written letters or replies should be reviewed before they leave the database.

Per-Record AI vs Whole-Database Questions

NeedBest approach
Summarise or classify each record's textAskAI per record, as above
Extract fields from attached documentsDocument AI into staging tables; see our scanning guide
Ask questions across tables in plain EnglishA natural-language query tool such as DatabaseAI
Sensitive data that must stay on siteA local model; see our private LLM guide

FAQ

Access and OpenAI API FAQ

Integration design, multi-user use and cost

Yes. Access VBA can send HTTPS requests with MSXML2.ServerXMLHTTP, the same way Excel VBA can. A function sends a field's text with an instruction, receives the JSON reply, and writes the answer back to a table. It works with Access 2016 and later on Windows, and with other AI providers that offer a REST API.

In separate fields or a separate table, never overwriting the source field. A typical design adds AIResult, AIStatus, AIModel and AIProcessedOn fields, or a child table that keeps one row per AI call, so you can re-run, compare models, and audit what was generated.

A synchronous call blocks Access for the few seconds the request takes. For single records that is acceptable. For batches, run the loop from a dedicated button with progress shown in the status bar and DoEvents between records, or move batch processing to a scheduled job outside the front end.

They can, but they should not process the same rows. Mark rows as 'Processing' with the user and time before calling the API, and skip rows already marked. For multi-user databases a single scheduled process is usually simpler and avoids shared API keys on every desktop.

Summarising long notes fields, classifying incoming requests or tickets, extracting fields from free text, drafting replies or letters from a record, and standardising messy names or addresses. For plain-English questions across the whole database, a dedicated natural-language query tool is a better fit than per-record API calls.