samayly.com is live. Hosted plans open soon. Join the waitlist →
Phone optionreference14 min read

Option 3: Apps Script + iOS Shortcut

Zero-cloud iPhone integration - uses Google Apps Script as a free webhook and iOS Shortcuts as the mobile client. No servers to maintain, no monthly cloud costs.

⏱️ Setup time: 30 min 💰 Cost: ~$1-3/month (Claude API only, Apps Script is free) 👥 Users: iOS-only (Shortcuts is Apple ecosystem)


What you're building

1. Raw prompt

2. Structured JSON

3. POST JSON

4. Validate + write

5. Confirmation

6. Success/error

7. Notification

📱 iOS Shortcut
(voice/text input)

🧠 Claude API
(parse to JSON)

📜 Google Apps Script
Web App endpoint

📊 Your Sheet


Setup Overview

Step 1
Create Apps Script Web App

Step 2
Deploy Web App (get URL)

Step 3
Get Claude API Key

Step 4
Build iOS Shortcut

Step 5
Test end-to-end

Step 6
Add to Siri + Home Screen


1Create Apps Script Web App

Apps Script is Google's built-in scripting environment - free, no server needed.

  1. Open your Google Sheet
  2. Extensions → Apps Script (opens a new tab)
  3. Delete the default myFunction code
  4. Paste this:
javascript
// ============================================
// TS-03 Tracker Web App
// ============================================

const VALID_SUBJECTS = [
  "FoVL-Language",
  "GPU Programming",
  "Ethics and Professional Life",
  "Innovation and IP Management",
  "Advanced AI"
];

const SHEET_ID = 'YOUR_SHEET_ID_HERE';  // Replace with your sheet ID
const SHEET_TAB = 'TS-03 Quiz & Assignment';

// Optional: shared secret to prevent unauthorized calls
const AUTH_SECRET = 'CHANGE_ME_TO_RANDOM_STRING_32_CHARS';


function doPost(e) {
  try {
    const data = JSON.parse(e.postData.contents);

    // Auth check
    if (data.secret !== AUTH_SECRET) {
      return jsonResponse({ error: "Unauthorized" }, 401);
    }

    const action = data.action;

    if (action === 'add') {
      return handleAdd(data.row);
    } else if (action === 'read') {
      return handleRead(data.query);
    } else if (action === 'update_status') {
      return handleUpdateStatus(data.row_num, data.new_status);
    }

    return jsonResponse({ error: "Unknown action: " + action });
  } catch (err) {
    return jsonResponse({ error: err.toString() });
  }
}


function handleAdd(row) {
  // Validate subject
  if (!VALID_SUBJECTS.includes(row.subject)) {
    return jsonResponse({
      error: `Invalid subject "${row.subject}". Valid: ${VALID_SUBJECTS.join(", ")}`
    });
  }

  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_TAB);

  // Find next # (count existing data rows)
  const existingNums = sheet.getRange('A5:A').getValues()
    .filter(r => r[0] && !isNaN(r[0]));
  const nextNum = existingNums.length + 1;

  // Append row
  sheet.appendRow([
    nextNum,
    row.subject,
    row.type || 'Assignment',
    row.title || '',
    row.due_date || '',
    row.time || '23:59 IST',
    'Google Classroom',
    'GCR',
    'Upcoming',
    '',  // Days Left (formula will populate)
    row.notes || ''
  ]);

  return jsonResponse({
    success: true,
    row_num: nextNum,
    message: `Added: ${row.subject} - ${row.title}`
  });
}


function handleRead(query) {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_TAB);
  const data = sheet.getRange('A4:K').getValues();
  const headers = data[0];
  const rows = data.slice(1).filter(r => r[0] !== '');

  // Simple filter examples
  const today = new Date();
  const weekFromNow = new Date(today.getTime() + 7 * 24 * 60 * 60 * 1000);

  let filtered = rows;
  const q = query.toLowerCase();

  if (q.includes('week') || q.includes('7 day')) {
    filtered = rows.filter(r => {
      const due = new Date(r[4]);
      return due >= today && due <= weekFromNow;
    });
  } else if (q.includes('today')) {
    filtered = rows.filter(r => {
      const due = new Date(r[4]);
      return due.toDateString() === today.toDateString();
    });
  } else if (q.includes('upcoming')) {
    filtered = rows.filter(r => r[8] === 'Upcoming');
  }

  // Format for iPhone display
  const summary = filtered.map(r =>
    `#${r[0]} ${r[1]} - ${r[3]} (due ${r[4]}, ${r[9]} days left)`
  ).join('\n');

  return jsonResponse({
    success: true,
    count: filtered.length,
    summary: summary || 'No matching items'
  });
}


function handleUpdateStatus(rowNum, newStatus) {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_TAB);
  // Row #N in the sheet is at row (N + 4) accounting for headers
  const sheetRow = parseInt(rowNum) + 4;
  sheet.getRange(sheetRow, 9).setValue(newStatus);  // Column I = Status
  return jsonResponse({
    success: true,
    message: `Updated row #${rowNum} status to ${newStatus}`
  });
}


function jsonResponse(obj, status = 200) {
  return ContentService
    .createTextOutput(JSON.stringify(obj))
    .setMimeType(ContentService.MimeType.JSON);
}
  1. Save (Cmd+S) - name the project TS-03 Tracker Web App
  2. Update: - SHEET_ID to your actual sheet ID - AUTH_SECRET to a random 32-char string (generate with openssl rand -hex 16 in Terminal)

2Deploy as Web App

  1. Top-right → Deploy → New deployment
  2. Click the gear icon → select Web app
  3. Configure: - Description: TS-03 Tracker v1 - Execute as: Me (your Google account - so it has sheet access) - Who has access: Anyone
  4. Click Deploy
  5. First time only: Authorize access → sign in → advanced → allow
  6. Copy the Web app URL - looks like: https://script.google.com/macros/s/AKfycb.../exec
  7. Save this URL - needed in Step 4

⚠️ "Anyone" access means anyone with the URL can call it - that's why we have AUTH_SECRET. Don't skip the secret check.


3Get Claude API Key

Same as Option 2 Step 2: 1. console.anthropic.com → API Keys → Create 2. Copy key (starts with sk-ant-...) 3. Add ~$5 credit for testing


4Build the iOS Shortcut

Open Shortcuts app on iPhone → + (top right) → Add Action

Add these 6 actions in order:

Action 1: Get input from user

Dictate Text (for voice) OR Ask for Input (for typing) - If Dictate: set "Stop Listening" to "On Tap" - If Ask: prompt = "What to add or query?"

Action 2: Get Claude to parse

Get Contents of URL - URL: https://api.anthropic.com/v1/messages - Method: POST - Headers: x-api-key: sk-ant-YOUR_KEY_HERE anthropic-version: 2023-06-01 content-type: application/json - Request Body: JSON json { "model": "claude-sonnet-4-5", "max_tokens": 512, "system": "You parse tracker requests into JSON. Valid subjects: FoVL-Language, GPU Programming, Ethics and Professional Life, Innovation and IP Management, Advanced AI. Return ONLY JSON with keys: action (add/read/update_status), subject, type, title, due_date (DD-Mmm-YYYY), time (HH:MM IST), notes. For reads, use action=read with a query field.", "messages": [ {"role": "user", "content": "REPLACE_WITH_ACTION_1_OUTPUT"} ] } Where REPLACE_WITH_ACTION_1_OUTPUT = tap the variable magic wand → select "Dictated Text" (or "Provided Input")

Action 3: Extract Claude's response

Get Dictionary Value - Get: Value for content in Contents of URL (from Action 2) - Then chain: Get Dictionary Value → Get: Value for 0.text

Result = the JSON string Claude generated.

Action 4: POST to Apps Script

Get Contents of URL - URL: paste your Apps Script Web App URL from Step 2 - Method: POST - Headers: content-type: application/json - Request Body: File → the extracted text from Action 3 - Wrap in a dictionary that adds the secret:

Actually easier - add another Dictionary action first: json { "secret": "YOUR_AUTH_SECRET", "action": "add", "row": {"subject": "...", ...} }

But simplest: modify the Claude system prompt to always include "secret": "YOUR_SECRET" in output.

Action 5: Parse response

Get Dictionary Value → get message field from Action 4's response.

Action 6: Speak / Show result

Speak Text (or Show Notification) - Text: value from Action 5


5Test the Shortcut

  1. Tap the shortcut's play button
  2. Speak or type: "Add GPU Programming quiz on Oct 15 at 6pm, 10 questions"
  3. Watch the actions execute
  4. Verify: - Response speaks/shows: "Added row #N: GPU Programming - Quiz" - Open your Google Sheet → new row appears

Try: - "What's due this week?" - "Show upcoming items" - "Mark row 5 as completed"


6Add to Siri and Home Screen

Add to Siri

  1. Shortcuts app → tap your shortcut → settings icon (top right)
  2. Add to Siri → record phrase: "tracker update" or "log this"

Add to Home Screen

  1. Same settings → Add to Home Screen
  2. Rename and pick an icon
  3. Now it's a one-tap app

Add to Apple Watch

  1. iPhone Shortcuts app → tap shortcut → toggle Show on Apple Watch
  2. Complication placement optional

Works on:

  • iPhone ✅
  • iPad ✅
  • Apple Watch ✅
  • Mac (Shortcuts app) ✅
  • HomePod ✅ (via Siri)
  • CarPlay ✅

Advantages over Options 1, 2, 4

  • No servers to maintain - Apps Script hosted by Google, free forever
  • No cloud vendor lock-in - everything runs in your Google account
  • Fast setup - 30 min vs 45-60 min for bots
  • Works offline for trigger - shortcut works, then syncs when online
  • Native iOS integration - Siri, Shortcuts, Focus Modes, Automations

Limitations

  • Apps Script has ~5-10 sec cold-start delay on first call after idle
  • iOS Shortcuts is iOS-only - Android friends need Option 2 or 4
  • Complex logic hard in Shortcuts - no if/else nesting like real code
  • Sharing = each user builds their own shortcut - no central deployment

Security note

  • Never commit the Apps Script URL to a public repo without the AUTH_SECRET check
  • Rotate AUTH_SECRET if you suspect compromise
  • Apps Script logs every call - check via Apps Script → Executions

Summary

You now have a Siri-callable, home-screen-launchable, Apple Watch-compatible way to update your sheet without any cloud infrastructure beyond Google's own free Apps Script. Best for personal iOS-heavy users.

Next: - Add Option 5 (Siri Voice) on top for full voice interaction - Or move to Option 2/4 (bots) if you need Android/desktop friends included

Questions or corrections: [email protected]