Base URL
All requests use your deployed Web App URL:
https://script.google.com/macros/s/[DEPLOYMENT_ID]/exec
Endpoints Overview
Method Action Description GET - Retrieve balance, expenses, and settings POST process_textAI-powered transaction processing or Q&A POST addManually add a transaction POST deleteDelete a transaction POST save_settingsSave user settings
GET Request
Retrieve current balance, recent expenses, budget, and privacy settings.
Implementation
function doGet () {
const sheet = getTargetSheet ();
const data = sheet . getDataRange (). getValues ();
const balance = calculateBalance ( sheet );
const budget = SCRIPT_PROP . getProperty ( "DAILY_BUDGET" ) || 350 ;
const privacyRaw = SCRIPT_PROP . getProperty ( "PRIVACY_MODE" );
const privacy = privacyRaw === null ? true : ( privacyRaw === "true" );
const expenses = [];
const startRow = Math . max ( 1 , data . length - 50 );
for ( let i = startRow ; i < data . length ; i ++ ) {
if ( data [ i ][ 0 ]) {
expenses . push ({
date: data [ i ][ 0 ],
description: data [ i ][ 1 ],
amount: data [ i ][ 2 ],
category: data [ i ][ 3 ],
type: data [ i ][ 4 ]
});
}
}
return ContentService . createTextOutput ( JSON . stringify ({
balance: balance ,
expenses: expenses ,
budget: budget ,
privacy: privacy
})). setMimeType ( ContentService . MimeType . JSON );
}
Request
curl https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec
Response
Current account balance calculated from all transactions
Array of the most recent 50 transactions Transaction date (ISO format or Google Sheets date)
Category: Food, Transport, Tech, Invest, Income, or Misc
Transaction type: CREDIT or DEBIT
Daily budget setting (defaults to “350”)
Privacy mode status (defaults to true)
Example Response
{
"balance" : 15420.50 ,
"expenses" : [
{
"date" : "2026-03-05T10:30:00.000Z" ,
"description" : "Coffee at Starbucks" ,
"amount" : 4.5 ,
"category" : "Food" ,
"type" : "DEBIT"
},
{
"date" : "2026-03-04T15:20:00.000Z" ,
"description" : "Salary" ,
"amount" : 5000 ,
"category" : "Income" ,
"type" : "CREDIT"
}
],
"budget" : "350" ,
"privacy" : true
}
POST Request
All POST requests require a JSON body with an action field.
Action: process_text
Use AI (Gemini) to process natural language for adding transactions or answering financial questions.
Implementation
if ( body . action === "process_text" ) {
const userText = body . text ;
const mode = body . mode ; // 'add' or 'ask'
// Fetches last 40 transactions for context
const historyData = sheet . getRange (
Math . max ( 1 , sheet . getLastRow () - 40 ), 1 , 41 , 4
). getValues ();
// Sends to Gemini Flash API with context
const aiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent?key= ${ geminiKey } ` ;
// Returns parsed transaction or answer
}
Request Body
Natural language input from user
Processing mode:
"add": Extract and save transaction
"ask": Answer financial question
Request Examples
Add Transaction
Ask Question
fetch
curl -X POST https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec \
-H "Content-Type: application/json" \
-d '{
"action": "process_text",
"text": "Spent 45 bucks on groceries",
"mode": "add"
}'
Response (mode: “add”)
Updated account balance after adding transaction
The extracted transaction details AI-determined category (Food, Transport, Tech, Invest, Income, Misc)
AI-determined type: CREDIT or DEBIT
AI-generated insight about the transaction (max 15 words)
User-facing message with insight
Example Response (add)
{
"status" : "Success" ,
"balance" : 15416.00 ,
"parsed" : {
"date" : "2026-03-05T14:25:00.000Z" ,
"description" : "Coffee" ,
"amount" : 4.5 ,
"category" : "Food" ,
"type" : "DEBIT" ,
"insight" : "Third coffee this week - maybe time for a home brew?"
},
"ai_response" : "Saved. Third coffee this week - maybe time for a home brew?"
}
Response (mode: “ask”)
AI-generated answer to the question (max 30 words)
Example Response (ask)
{
"status" : "Success" ,
"ai_response" : "You spent $127.50 on food this week across 8 transactions."
}
Error Response
{
"status" : "Error" ,
"message" : "AI Failed: Invalid API key"
}
Action: add
Manually add a transaction without AI processing (legacy/direct method).
Implementation
if ( body . action === "add" ) {
const date = new Date ();
const type = ( body . type === 'income' || body . category === 'Income' )
? 'CREDIT' : 'DEBIT' ;
sheet . appendRow ([ date , body . description , body . amount , body . category , type ]);
const newBalance = calculateBalance ( sheet );
return ContentService . createTextOutput ( JSON . stringify ({
status: "Success" ,
balance: newBalance ,
parsed: { date , description: body . description , amount: body . amount , category: body . category , type }
})). setMimeType ( ContentService . MimeType . JSON );
}
Request Body
Transaction amount (positive number)
Category: Food, Transport, Tech, Invest, Income, or Misc
Transaction type: "income" for CREDIT, anything else for DEBIT
Request Examples
curl -X POST https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec \
-H "Content-Type: application/json" \
-d '{
"action": "add",
"description": "Lunch at Chipotle",
"amount": 12.50,
"category": "Food"
}'
Response
Updated account balance after adding transaction
Echo of the added transaction Transaction timestamp (server time)
Example Response
{
"status" : "Success" ,
"balance" : 15403.50 ,
"parsed" : {
"date" : "2026-03-05T14:30:00.000Z" ,
"description" : "Lunch at Chipotle" ,
"amount" : 12.50 ,
"category" : "Food" ,
"type" : "DEBIT"
}
}
Action: delete
Delete a transaction by matching description and amount.
Implementation
if ( body . action === "delete" ) {
const data = sheet . getDataRange (). getValues ();
// Search from bottom to top (most recent first)
for ( let i = data . length - 1 ; i >= 0 ; i -- ) {
if ( data [ i ][ 1 ] === body . description && data [ i ][ 2 ] == body . amount ) {
sheet . deleteRow ( i + 1 );
const newBalance = calculateBalance ( sheet );
return ContentService . createTextOutput ( JSON . stringify ({
status: "Deleted" ,
balance: newBalance
})). setMimeType ( ContentService . MimeType . JSON );
}
}
return ContentService . createTextOutput ( JSON . stringify ({
status: "Not Found"
})). setMimeType ( ContentService . MimeType . JSON );
}
Request Body
Exact description of transaction to delete
Exact amount of transaction to delete
The script searches from the most recent transaction backwards and deletes the first match found.
Request Examples
curl -X POST https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec \
-H "Content-Type: application/json" \
-d '{
"action": "delete",
"description": "Coffee at Starbucks",
"amount": 4.5
}'
Response (Success)
Updated account balance after deletion
{
"status" : "Deleted" ,
"balance" : 15420.50
}
Response (Not Found)
{
"status" : "Not Found"
}
Action: save_settings
Save user settings to Script Properties.
Implementation
if ( body . action === "save_settings" ) {
SCRIPT_PROP . setProperty ( "DAILY_BUDGET" , body . budget . toString ());
SCRIPT_PROP . setProperty ( "PRIVACY_MODE" , body . privacy . toString ());
if ( body . geminiKey ) SCRIPT_PROP . setProperty ( "GEMINI_KEY" , body . geminiKey );
return ContentService . createTextOutput ( JSON . stringify ({
status: "Saved"
})). setMimeType ( ContentService . MimeType . JSON );
}
Request Body
Gemini API key (optional, only if updating)
Request Examples
curl -X POST https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec \
-H "Content-Type: application/json" \
-d '{
"action": "save_settings",
"budget": 500,
"privacy": false,
"geminiKey": "AIzaSy..."
}'
Response
Helper Functions
calculateBalance()
Calculates the current balance by iterating through all transactions.
function calculateBalance ( sheet ) {
const data = sheet . getDataRange (). getValues ();
let bal = 0 ;
// Start from row 1 to skip header
for ( let i = 1 ; i < data . length ; i ++ ) {
const amount = parseFloat ( data [ i ][ 2 ]); // Column C
const type = data [ i ][ 4 ]; // Column E
if ( ! isNaN ( amount )) {
if ( type === 'CREDIT' ) {
bal += amount ;
} else {
bal -= amount ; // DEBIT or blank
}
}
}
return bal ;
}
This function is called after every add/delete operation to ensure balance accuracy.
getTargetSheet()
Retrieve the first sheet from the configured Google Sheet.
function getTargetSheet () {
const ss = SpreadsheetApp . openById ( SHEET_ID );
return ss . getSheets ()[ 0 ];
}
AI Prompts
The process_text action uses different prompts based on mode:
Add Mode Prompt
You are a financial assistant .
CONTEXT : The user wants to ADD a transaction .
USER INPUT : "{userText}"
HISTORY : { last 40 transactions }
TASK :
1. Extract : Description , Amount , Category ( Food , Transport , Tech , Invest , Income , Misc ).
2. Analyze : Compare this new expense to the HISTORY . Is it higher than usual ? Is it a repeat ?
3. Insight : Write a very short , witty , or helpful insight ( max 15 words ) about this specific spend .
OUTPUT JSON STRICTLY :
{
"description" : "String" ,
"amount" : Number ,
"category" : "String" ,
"type" : "DEBIT" or "CREDIT" ,
"insight" : "String"
}
Ask Mode Prompt
You are a financial analyst .
CONTEXT : The user is ASKING a question about their finances .
USER INPUT : "{userText}"
HISTORY : { last 40 transactions }
TASK :
Answer the user 's question based strictly on the HISTORY data provided.
Be concise ( max 30 words ).
OUTPUT JSON STRICTLY :
{
"answer" : "String"
}
Rate Limits & Quotas
Google Apps Script Limits
URL Fetch calls per day : 20,000 (for Gemini API calls)
Script runtime : 6 minutes per execution
Triggers : 90 minutes of total runtime per day
Gemini API Limits
Depends on your API tier. Free tier typically includes:
15 requests per minute
1 million tokens per minute
1,500 requests per day
See Gemini API Quotas for current limits.
Error Handling
All endpoints return JSON responses. Common error patterns:
{
"status" : "Error" ,
"message" : "Description of error"
}
Common Errors
Error Message Cause Solution AI Failed: Invalid API keyInvalid or missing GEMINI_KEY Check Script Properties AI Failed: Quota exceededGemini API rate limit hit Wait or upgrade API tier Not FoundDelete action didn’t find match Verify description/amount Script function not foundScript not properly deployed Redeploy as Web App
Next Steps
Setup Guide Complete deployment instructions
Frontend Integration Connect your app to the API