, ,

How I Built a CustomGPT to Manage Development Invoices and Bill Payments at RV@Olympic

How to Build a CustomGPT to Manage Your Development Invoices and Bill Payments

As we are currently working to develop our RV park, RV@Olympic, we are dealing with the all too familiar monotonous processes that come with any development project. One of these tasks is organizing, managing, and processing invoices to pay our subcontractors. It is a repeatable task that is ripe for automation. While not 100% automated, using a CustomGPT I’ve just built has made this time-consuming process a lot easier. Inspired by Spencer’s post on Connecting a Google Doc to Your Custom GPT, I decided to share a version 1.0 of the RV@Olympic Invoice Manager and show you how you can do this as well.

Note on Security. This post is not covering authentication methods. For a Google Sheet containing proprietary and confidential applications, consider adding authentication processes.

Below is a step-by-step guide on how to do it along with a video to walk you through it as well.

CustomGPT Invoice Manager


Video Overview

The video below will walk you through an overview of the custom GPT and how I went about building it. I recommend using both this and the detailed instructions below as the video may gloss over some items that are more detailed in the outlined process in text below.


Step 1: Set Up Your Google Doc Template

Before diving into coding, design your Google Doc or Google Sheet template to reflect the information you’ll need for invoices. For my setup, I used the following columns:

  • Invoice Number
  • Vendor Name
  • Description
  • Amount Due
  • Amount Paid
  • Status (Paid or Unpaid)
  • Due Date

This template provides the foundation for your invoice management system, allowing for consistent and structured data entry.


Step 2: Define Your CustomGPT’s Required Functionality

Consider the key functions you’d like this GPT to perform. For RV@Olympic, I needed:

  • Calculating total outstanding amounts
  • Listing unpaid or overdue invoices
  • Adding a new invoice
  • Updating the status of an invoice
  • Retrieving invoices due within a specific date range

By establishing these needs first, you’ll have a clear map for the script and avoid unnecessary functionality that may complicate the system.


Step 3: Understand CustomGPTs – What They Can and Cannot Do

It’s important to know that CustomGPTs can only retrieve data or perform tasks if supported by an external script. They can trigger scripts, display results, and prompt data entry, but they can’t directly edit Google Sheets or perform calculations themselves. While this is incredible in itself, the magic with the GPT is that it can also interpret things you say in many different ways and with a very high probability still understand what it should do for you and do what you need. This is important as the end user doesn’t need to know any coding or any specific commands, but can just type a request normally as they would to a human and it can trigger the correct function to get you what you need, provided you have set things up correctly.

The functionality of your GPT depends on the accuracy of the Apps Script you create—making this script a critical first step. But don’t fret, because you have an expert. And it’s not me, it’s ChatGPT.


Step 4: Writing the Apps Script

The core of this project is a Google Apps Script that handles invoice management tasks within the Google Sheet. This script processes different actions, such as calculating totals, updating statuses, or retrieving date-specific invoices.

  1. Open Your Google Sheet: Start by opening the Google Sheet where you want to run your Apps Script.
  2. Access the Extensions Menu: In the top menu, click on Extensions.
  3. Select Apps Script: In the Extensions dropdown, select Apps Script. This will open a new tab with the Google Apps Script editor.
  4. Write or Paste Your Script: In the Apps Script editor, you can start writing your script or paste in the script you have prepared.
  5. Save Your Script: Click the File menu and select Save, or just press Cmd+S (Mac) or Ctrl+S (Windows) to save your work.

How to Prompt ChatGPT to Write the Script for You

If you’re looking to have ChatGPT generate parts or all of the script, you can use prompts tailored to your needs. Based on the functionality outlined in Step 2, here’s how you might structure your prompts to get targeted results.

Example Prompts for Each Function:

  1. Total Amount Due Calculation:
    • Prompt: “Write a Google Apps Script function that calculates the total amount due for all unpaid invoices in a Google Sheet. The sheet has columns for Invoice Number, Vendor Name, Description, Amount Due, Amount Paid, Status, and Due Date. Only include invoices with the status ‘Unpaid.’”
  2. Listing Invoices Due This Week:
    • Prompt: “Can you write a function in Google Apps Script to retrieve all unpaid invoices due this week? The function should filter based on the Due Date column, assuming the week starts on Sunday.”
  3. Overdue Invoices:
    • Prompt: “Create a function that retrieves all overdue invoices from a Google Sheet. Only include invoices with the status ‘Unpaid’ where the Due Date is before today.”
  4. Adding a New Invoice:
    • Prompt: “Write a Google Apps Script function to add a new invoice row to my Google Sheet. The function should take inputs for Invoice Number, Vendor Name, Description, Amount Due, Amount Paid, Status, and Due Date and append them as a new row.”
  5. Updating Invoice Status:
    • Prompt: “Create a Google Apps Script function that updates the status of a specific invoice in my Google Sheet. The function should take the Invoice Number and the newStatus as inputs to locate and update the invoice status.”
  6. Retrieving Invoices Between Dates:
    • Prompt: “Write a function that retrieves invoices with a Due Date between two specified dates. The function should return all relevant details if the invoice status is ‘Unpaid’.”

General Prompt for Generating the Full Script:

You could also ask ChatGPT to create the entire script based on a high-level description of the project:

  • Prompt: “Write a Google Apps Script to manage invoices in a Google Sheet. I need functions for calculating the total unpaid amount, listing unpaid invoices due this week, retrieving overdue invoices, adding a new invoice, updating an invoice’s status, and listing invoices within a specific date range. Each invoice has fields for Invoice Number, Vendor Name, Description, Amount Due, Amount Paid, Status, and Due Date.”

The Full Script for RV@Olympic Invoice Manager

[Note: If you are using this direct code, please replace YOUR_SPREADSHEET_ID with your actual Google Sheet id, which is the long string of letters and numbers between ‘d/’ and ‘/edit…’ in the address bar.]
Full Apps Script (Starting with the doGet function)
function doGet(e) {
  const { action, invoiceNumber, vendorName, description, amountDue, amountPaid, status, dueDate, newStatus, startDate, endDate } = e.parameter;
  
  Logger.log("Received action: " + action);
  Logger.log("Received invoiceNumber: " + invoiceNumber);
  Logger.log("Received newStatus: " + newStatus); // Log newStatus to verify it is passed

  switch (action) {
    case 'totalAmountDue':
      return ContentService.createTextOutput(getTotalAmountDue());
    case 'invoicesDueThisWeek':
      return ContentService.createTextOutput(JSON.stringify(getInvoicesDueThisWeek()));
    case 'overdueInvoices':
      return ContentService.createTextOutput(JSON.stringify(getOverdueInvoices()));
    case 'allUnpaidInvoices':
      return ContentService.createTextOutput(JSON.stringify(getAllUnpaidInvoices()));
    case 'addInvoice':
      return ContentService.createTextOutput(addInvoiceLine(invoiceNumber, vendorName, description, amountDue, amountPaid, status, dueDate));
    case 'getUnpaidInvoices':
      return ContentService.createTextOutput(getUnpaidInvoices());
    case 'getPaidInvoices':
      return ContentService.createTextOutput(getPaidInvoices());
    case 'updateInvoiceStatus':
      Logger.log("Updating status for invoiceNumber: " + invoiceNumber + " to newStatus: " + newStatus);
      return ContentService.createTextOutput(updateInvoiceStatus(invoiceNumber, newStatus));
    case 'getInvoicesBetweenDates':
      Logger.log("Fetching invoices between " + startDate + " and " + endDate);
      return ContentService.createTextOutput(getInvoicesBetweenDates(startDate, endDate))
                           .setMimeType(ContentService.MimeType.JSON);
    default:
      return ContentService.createTextOutput('Action not recognized');
  }
}
Explanation of doGet Function

The doGet function acts as the entry point for all actions triggered by your CustomGPT. When the CustomGPT sends a request, doGet reads the parameters provided (like action, invoiceNumber, or newStatus) and routes them to the appropriate function based on the action type. Here’s how it works:

    • Parameters: doGet reads various parameters, such as action and invoiceNumber, provided in the query.
    • Switch Statement: The function uses a switch statement to direct the request to the corresponding function (e.g., getTotalAmountDue for calculating total dues or updateInvoiceStatus for changing invoice status).
    • Response: After processing, it returns a response to the CustomGPT, either as a plain text or JSON string, which is then displayed to the user.

getTotalAmountDue Function
function getTotalAmountDue() {
  var spreadsheetId = 'YOUR_SPREADSHEET_ID'; // Replace YOUR_SPREADSHEET_ID with the actual ID.
  try {
    var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName("Invoices");
    var data = sheet.getDataRange().getValues();
    var totalDue = 0;
    console.log("Total Rows: " + data.length); // Log the total number of rows
    for (var i = 1; i < data.length; i++) { // Start from 1 to skip header
      console.log("Processing row " + i + ": " + data[i]); // Log each row being processed
      if (data[i][5] === "Unpaid") {
        var amountDue = typeof data[i][3] === 'number' ? data[i][3] : parseFloat(data[i][3].replace(/[$,]/g, ''));
        console.log("Adding " + amountDue + " to total due."); // Log amount being added
        totalDue += amountDue;
      }
    }
    console.log("Final Total Amount Due: " + totalDue); // Log final total amount due
    return totalDue;
  } catch (e) {
    Logger.log("Error: " + e.toString());
    return "Error: " + e.toString(); // Returns error message for easier debugging
  }
}

getInvoicesDueThisWeek Function
function getInvoicesDueThisWeek() {
  var spreadsheetId = 'YOUR_SPREADSHEET_ID'; // Your spreadsheet ID
  try {
    var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName("Invoices");
    var data = sheet.getDataRange().getValues();
    
    var today = new Date(); // Get today's date
    today.setHours(0, 0, 0, 0); // Normalize today's date to the start of the day
    var startOfWeek = new Date(today);
    startOfWeek.setDate(today.getDate() - today.getDay()); // Set to the previous Sunday
    var endOfWeek = new Date(startOfWeek);
    endOfWeek.setDate(startOfWeek.getDate() + 6); // Set to the next Saturday

    var dueThisWeek = [];
    var totalAmountDueThisWeek = 0; // Initialize the total amount due for invoices due this week
    console.log("Checking invoices due this week:");
    for (var i = 1; i < data.length; i++) { // Skip header row var dueDate = new Date(data[i][6]); dueDate.setHours(0, 0, 0, 0); // Normalize due date to the start of the day if (dueDate >= startOfWeek && dueDate <= endOfWeek && data[i][5] === "Unpaid") {
        var invoiceDetails = {
          invoiceNumber: data[i][0],
          vendorName: data[i][1],
          description: data[i][2],
          amountDue: data[i][3],
          amountPaid: data[i][4],
          status: data[i][5],
          dueDate: dueDate.toDateString()  // Convert date to a readable string format
        };
        dueThisWeek.push(invoiceDetails);
        totalAmountDueThisWeek += parseFloat(data[i][3]);
        console.log("Invoice " + data[i][0] + " is due this week. Details: ", JSON.stringify(invoiceDetails));
      }
    }
    console.log("Total invoices due this week: " + dueThisWeek.length);
    console.log("Total amount due this week: $" + totalAmountDueThisWeek.toFixed(2));

    // Creating a result object to include all required information
    var result = {
      totalInvoices: dueThisWeek.length,
      totalAmount: totalAmountDueThisWeek.toFixed(2),
      details: dueThisWeek
    };

    return JSON.stringify(result); // Return JSON string with detailed info
  } catch (e) {
    Logger.log("Error: " + e.toString());
    return "Error: " + e.toString();
  }
}

getOverdueInvoices Function
function getOverdueInvoices() {
  var spreadsheetId = 'YOUR_SPREADSHEET_ID';  // Spreadsheet ID
  var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName("Invoices");
  var data = sheet.getDataRange().getValues();
  var today = new Date();
  today.setHours(0, 0, 0, 0);  // Normalize today's date to the start of the day
  var overdueInvoices = [];
  var totalOverdueAmount = 0;
  console.log("Today's Date (normalized): " + today);

  for (var i = 1; i < data.length; i++) {  // Skip header row
    var dueDate = new Date(data[i][6]);  // Assuming 'Due Date' is in column 7
    dueDate.setHours(0, 0, 0, 0);  // Normalize due date
    if (dueDate < today && data[i][5].trim() === "Unpaid") {
      var invoiceDetails = {
        invoiceNumber: data[i][0],
        vendorName: data[i][1],
        description: data[i][2],
        amountDue: data[i][3],
        amountPaid: data[i][4],
        status: data[i][5],
        dueDate: dueDate.toDateString()  // Convert date to a readable string format
      };
      overdueInvoices.push(invoiceDetails);
      totalOverdueAmount += parseFloat(data[i][3]);  // Assuming 'Amount Due' is in column 4
      console.log("Invoice " + data[i][0] + " is overdue. Details: ", JSON.stringify(invoiceDetails));
    }
  }

  console.log("Total overdue invoices: " + overdueInvoices.length);
  console.log("Total overdue amount: $" + totalOverdueAmount.toFixed(2));

  // Creating a result object to include all required information
  var result = {
    totalInvoices: overdueInvoices.length,
    totalAmount: totalOverdueAmount.toFixed(2),
    details: overdueInvoices
  };

  return JSON.stringify(result);  // Return JSON string with detailed overdue invoices info
}

getAllUnpaidInvoices Function
function getAllUnpaidInvoices() {
  var spreadsheetId = 'YOUR_SPREADSHEET_ID';  // Spreadsheet ID
  try {
    var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName("Invoices");
    var data = sheet.getDataRange().getValues();
    var unpaidInvoices = [];
    var totalUnpaidAmount = 0; // Variable to store the total amount due from unpaid invoices
    console.log("Gathering all unpaid invoices:");
    for (var i = 1; i < data.length; i++) {  // Skip header row
      if (data[i][5] === "Unpaid") {  // Check if the invoice is unpaid
        unpaidInvoices.push(data[i]);
        totalUnpaidAmount += parseFloat(data[i][3]); // Assuming 'Amount Due' is in column 4
        console.log("Invoice " + data[i][0] + " is unpaid. Details: ", data[i]);  // Log detailed invoice data
      }
    }
    console.log("Total unpaid invoices: " + unpaidInvoices.length);
    console.log("Total amount due from unpaid invoices: $" + totalUnpaidAmount.toFixed(2)); // Log the total amount due
    return unpaidInvoices;  // Returns array of unpaid invoices
  } catch (e) {
    Logger.log("Error: " + e.toString());
    return "Error: " + e.toString();
  }
}

addInvoiceLine Function
function addInvoiceLine(invoiceNumber, vendorName, description, amountDue, amountPaid, status, dueDate) {
  try {
    const spreadsheetId = 'YOUR_SPREADSHEET_ID';  // Replace YOUR_SPREADSHEET_ID with the actual ID.
    const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName("Invoices");

    // Parse dueDate as a UTC date
    const dateParts = dueDate.split("-");
    const formattedDueDate = new Date(Date.UTC(dateParts[0], dateParts[1] - 1, dateParts[2])); // Month is 0-indexed

    Logger.log("Parsed formattedDueDate in UTC: " + formattedDueDate); // Log to confirm the date

    if (isNaN(formattedDueDate.getTime())) {
      throw new Error("Invalid date format provided for dueDate");
    }

    // Format date explicitly to 'yyyy-MM-dd' to prevent time zone shifts when added to the sheet
    const formattedDateString = Utilities.formatDate(formattedDueDate, "UTC", "yyyy-MM-dd");

    // Append the row to the sheet, using the formatted date string
    sheet.appendRow([invoiceNumber, vendorName, description, amountDue, amountPaid, status, formattedDateString]);
    
    return "Invoice line added successfully.";
  } catch (error) {
    Logger.log("Error in addInvoiceLine: " + error.message);
    return "Error: " + error.message;
  }
}

getUnpaidInvoices Function
function getUnpaidInvoices() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Invoices");
  const data = sheet.getDataRange().getValues();
  const unpaidInvoices = data.filter(row => row[5] === "Unpaid").map(row => {
    return { invoiceNumber: row[0], details: row.slice(1) };
  });
  return JSON.stringify(unpaidInvoices);
}

getPaidInvoices Function
function getPaidInvoices() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Invoices");
  const data = sheet.getDataRange().getValues();
  const paidInvoices = data.filter(row => row[5] === "Paid").map(row => {
    return { invoiceNumber: row[0], details: row.slice(1) };
  });
  return JSON.stringify(paidInvoices);
}

updateInvoiceStatus Function
function updateInvoiceStatus(invoiceNumber, newStatus) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Invoices");
  const data = sheet.getDataRange().getValues();
  const rowIndex = data.findIndex(row => row[0] === invoiceNumber);
  if (rowIndex !== -1) {
    sheet.getRange(rowIndex + 1, 6).setValue(newStatus); // assuming status is in the 6th column
    return "Invoice " + invoiceNumber + " status updated to " + newStatus;
  } else {
    return "Invoice not found";
  }
}

getInvoicesBetweenDates Function
function getInvoicesBetweenDates(startDate, endDate) {
  var spreadsheetId = 'YOUR_SPREADSHEET_ID'; // Your spreadsheet ID
  var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName("Invoices");
  var data = sheet.getDataRange().getValues();
  var filteredInvoices = [];
  var totalAmountDue = 0;
  
  // Parse the provided dates
  var start = new Date(startDate);
  var end = new Date(endDate);
  start.setHours(0, 0, 0, 0);
  end.setHours(23, 59, 59, 999);

  // Filtering invoices within the date range
  for (var i = 1; i < data.length; i++) { // Skip header row var dueDate = new Date(data[i][6]); if (dueDate >= start && dueDate <= end) {
      filteredInvoices.push(data[i]);
      totalAmountDue += parseFloat(data[i][3]); // Assuming 'Amount Due' is in the fourth column
    }
  }

  var result = {
    totalInvoices: filteredInvoices.length,
    totalAmountDue: totalAmountDue.toFixed(2),
    invoices: filteredInvoices
  };

  return JSON.stringify(result); // Return a JSON string of the result
}

Step 5: Testing and Deploying the Script in Google Apps Script

Once you’ve written your script, it’s crucial to test each function to ensure everything works as expected. Google Apps Script provides a built-in editor and logger to help with testing and debugging.

Testing the Functions

To test each function individually, you can ask ChatGPT to create test functions for each function you create and test functions in the Apps Script editor:

  1. Add a new function at the bottom of your script, such as testGetTotalAmountDue for testing getTotalAmountDue.
  2. Within this test function, call the function you want to test and use Logger.log() to view the output. ChatGPT will likely automatically do this for you upon request.
  3. Run the test function by selecting it in the drop-down menu and clicking Run.
  4. View logs by going to View > Logs to confirm the output matches your expectations.

Here’s an example of a simple test function:

function testGetTotalAmountDue() {
  var result = getTotalAmountDue();
  Logger.log("Total Amount Due: " + result);
}

Deploying the Script

Once your functions are working correctly, deploy the script as a web app:

  1. In the Google Apps Script editor, click on Deploy > Test deployments > New deployment.
  2. Select Web app as the deployment type.
  3. Set the access level to Anyone with the link to allow your CustomGPT to access the script.
  4. Click Deploy, then copy the web app URL that’s generated.

This URL will be the endpoint that your CustomGPT will use to interact with your script.


Step 6: Configuring Your CustomGPT – Starting from Scratch

Now that your script is live, it’s time to set up the CustomGPT that will connect to it. Follow these steps to create and configure a Custom GPT from scratch.

Step-by-Step Guide to Starting a New Custom GPT

  1. Open ChatGPT: Start by logging into your ChatGPT account.
  2. Go to Explore GPTs: In the left-hand sidebar, click on Explore GPTs. This opens the GPT marketplace where you can browse existing GPTs or create a new one.
  3. Select “Create a Custom GPT”: Scroll to the top of the Explore GPTs page and click the Create a Custom GPT button to start setting up your Custom GPT.
  4. Name Your Custom GPT: Choose a name that reflects its purpose, such as “Invoice Manager for RV@Olympic.”
  5. Provide a Description: In the description, briefly explain the GPT’s purpose (e.g., “A CustomGPT for managing development invoices, tracking payments, and updating invoice statuses.”).
  6. Instructions: In the Instructions field, provide a high-level overview of tasks you want the Custom GPT to assist with. Include specific requests or commands you anticipate using, such as:
    • “Retrieve all unpaid invoices and provide a summary of total outstanding amounts.”
    • “Add new invoices, update the status of invoices, and list invoices due within a specified date range.”
      I used ChatGPT to create this for me by providing it my script.
  7. Conversation Starters: Use this section to create sample questions or prompts that users might start with. Example starters could include:
    • “What invoices are due this week?”
    • “Can you add a new invoice for Vendor A?”
    • “How much is the total amount due for all unpaid invoices?”
  8. Knowledge Base: If there’s background information or context the Custom GPT should know (like details on your invoice structure or specific terminology used), you can add it here to improve response accuracy. I left this part blank.
  9. Actions: Configure action. This is covered in the next step.

Step 7: Setting Up an Action and Schema in ChatGPT

Creating a schema is a crucial step when defining actions in your CustomGPT. The schema serves as a blueprint that outlines the structure of the data, defining what inputs (parameters) the GPT should accept, what actions it can trigger, and what kind of responses to expect. In essence, it tells ChatGPT how to interact with your Google Apps Script endpoint.

Why You Need a Schema

The schema acts as the API contract between ChatGPT and your Google Apps Script. Without it, ChatGPT wouldn’t know how to pass data to the script or interpret the results. It specifies:

  • The Available Actions: This includes all the tasks you defined, like listing unpaid invoices, adding invoices, or updating status.
  • Required Parameters: For each action, you can specify mandatory parameters (like invoiceNumber or status for updateInvoiceStatus) to ensure accurate processing.
  • Expected Responses: By setting response formats, you make sure the GPT displays results in a readable and understandable way.

Creating a schema also enables error handling and validation. If a parameter is missing or incorrectly formatted, the schema helps catch these issues before they affect the results.

Steps to Create the Schema

  1. In the Configure section in your CustomGPT, navigate to Actions.
  2. Click on Create New Action to start configuring your custom schema.
  3. Click on Get Help From ActionsGPT and from there, have a conversation with ActionsGPT letting it know that you need a schema for executing and interacting with Google Sheet and AppsScript. I recommend providing it with the following:
    1. your web app URL from Apps Script
    2. the web address of the Google Sheet
    3. The script from Apps Script

Here’s the schema for the RV@Olympic Invoice Manager you can also simply drop in and just adjust the relevant info for your sheet:

openapi: 3.1.0
info:
  title: Google Apps Script Invoice API
  description: API for managing invoice operations in Google Sheets.
  version: 1.1.0
servers:
  - url: YOUR_DEPLOYMENT_URL_HERE
paths:
  /exec:
    get:
      summary: Perform an action on invoice data
      parameters:
        - name: action
          in: query
          required: true
          schema:
            type: string
            enum:
              - "totalAmountDue"
              - "invoicesDueThisWeek"
              - "overdueInvoices"
              - "allUnpaidInvoices"
              - "addInvoice"
              - "getUnpaidInvoices"
              - "getPaidInvoices"
              - "updateInvoiceStatus"
              - "getInvoicesBetweenDates"
      responses:
        '200':
          description: Success response
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: Response message or JSON data containing requested invoice details.

Troubleshooting and Validation

Schemas can be sensitive to formatting. If you encounter an error, check your schema’s syntax using tools like YAML Lint or Swagger Editor. These tools help identify and correct formatting errors, ensuring that ChatGPT can read your schema without issues.

By creating a robust schema, you’re establishing a clear and reliable way for ChatGPT to communicate with your Apps Script, making the system more efficient and error-resistant.


Step 8: Testing and Publishing Your Custom GPT

Now that your Custom GPT and actions are set up, it’s time to test and publish it.

Testing Your Custom GPT

  1. Return to the Custom GPT setup page, and locate the **Test** option.
  2. Enter sample prompts you expect to use, such as:
    • “What is the total amount due for unpaid invoices?”
    • “List all invoices due this week.”
    • “Add a new invoice for Vendor X with $500 due next week.”
  3. Review the results to confirm that the GPT is correctly interacting with your Google Apps Script. Make sure the responses match what your script is designed to deliver.
  4. If you encounter any errors, double-check your schema and actions configuration for accuracy.

Validating Your Schema (If Needed)

If you notice issues with data handling, use tools like YAML Lint or Swagger Editor to validate your schema formatting. These tools help identify and correct any syntax errors that could interfere with your GPT’s performance.

Publishing Your Custom GPT

  1. Once testing is successful, return to the Custom GPT setup page.
  2. Click Publish to make your GPT available for use. Now, it’s ready to assist with managing invoices and bill payments in your development project.

Conclusion

With this guide, you’ve successfully built and deployed a Custom GPT designed to streamline your invoice management process. By integrating Google Apps Script with ChatGPT, you’ve created a powerful tool that automates repetitive tasks, retrieves data on-demand, and simplifies financial tracking for your projects.

This setup can be expanded and adapted to other projects, allowing you to build a custom solution tailored to your needs. Whether managing invoices, tracking payments, or updating records, this Custom GPT offers an efficient, personalized approach to workflow automation.

Enjoy using your new invoice manager, and explore other ways that Custom GPTs and Google Apps Script can elevate your project management!


About the Author: Michael has spent a decade working in various capacities on more than $7 billion of real estate transactions spanning all asset classes and geographies throughout the USA. Most recently, Michael was a founding member and COO of Stablewood Properties, an institutionally backed real estate operator. Before Stablewood, Michael was at Hines in San Francisco where he primarily worked on 2 high-rise mixed-use development projects totaling 2 million square feet.  Michael has both an MBA and Master in Real Estate with a concentration in Real Estate Finance from Cornell University.