Guides11 min readPublished 2026-08-03

How to Call an OpenAI-Compatible API from Google Sheets

Call an OpenAI-compatible API from Google Sheets with Apps Script, protected Script Properties, intentional batch runs, locks and quota controls.

By LumeAPI Engineering Team

Multi-Model API hub →

Short path: OpenAI-compatible API · Multi-model API · AI API pricing · Models

Last verified: August 3, 2026

To call an OpenAI-compatible API from Google Sheets, use a bound Apps Script, store the inference key in Script Properties, send JSON with UrlFetchApp.fetch, and write results back in one batch. Prefer a menu action over a volatile custom formula: spreadsheet recalculation can otherwise create repeated API calls, unpredictable cost and duplicate output.

The example below processes selected prompts from column A and writes answers to column B through LumeAPI's Chat Completions endpoint.

Set up the sheet and secret

  1. Create a sheet with Prompt in A1 and Answer in B1.
  2. Choose Extensions → Apps Script.
  3. In Project Settings → Script Properties, add:
  • LUMEAPI_API_KEY: your inference key
  • LUMEAPI_MODEL: gpt-5.6-terra or another current catalog ID
  1. Paste the script below and save it.
  2. Reload the spreadsheet and use the new LumeAPI menu.

Google documents Script Properties as project-scoped key-value storage. They are safer than placing a key in a cell or source file, but anyone with sufficient script-project access may still be able to view or use them. Choose access controls accordingly.

Runnable Apps Script

javascript
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('LumeAPI')
    .addItem('Generate answers for selected rows', 'generateSelectedAnswers')
    .addToUi();
}

function generateSelectedAnswers() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const range = sheet.getActiveRange();

  if (!range || range.getColumn() !== 1) {
    throw new Error('Select one or more prompt cells in column A.');
  }

  const lock = LockService.getDocumentLock();
  lock.waitLock(5000);

  try {
    const prompts = range.getValues();
    const answers = prompts.map(([value]) => {
      const prompt = String(value || '').trim();
      if (!prompt) return [''];

      try {
        return [callLumeApi_(prompt)];
      } catch (error) {
        return [`ERROR: ${error.message}`];
      }
    });

    sheet
      .getRange(range.getRow(), 2, answers.length, 1)
      .setValues(answers);
  } finally {
    lock.releaseLock();
  }
}

function callLumeApi_(prompt) {
  const properties = PropertiesService.getScriptProperties();
  const apiKey = properties.getProperty('LUMEAPI_API_KEY');
  const model = properties.getProperty('LUMEAPI_MODEL') || 'gpt-5.6-terra';

  if (!apiKey) {
    throw new Error('Missing LUMEAPI_API_KEY in Script Properties.');
  }

  const response = UrlFetchApp.fetch(
    'https://api.lumeapi.site/v1/chat/completions',
    {
      method: 'post',
      contentType: 'application/json',
      headers: {
        Authorization: `Bearer ${apiKey}`,
      },
      payload: JSON.stringify({
        model,
        messages: [
          {
            role: 'system',
            content: 'Answer clearly. Do not invent missing facts.',
          },
          { role: 'user', content: prompt },
        ],
        temperature: 0,
      }),
      muteHttpExceptions: true,
    },
  );

  const status = response.getResponseCode();
  const text = response.getContentText();
  let data;

  try {
    data = JSON.parse(text);
  } catch (error) {
    throw new Error(`HTTP ${status}: response was not JSON`);
  }

  if (status < 200 || status >= 300) {
    const message = data.error && data.error.message
      ? data.error.message
      : text.slice(0, 300);
    throw new Error(`HTTP ${status}: ${message}`);
  }

  const answer = data.choices &&
    data.choices[0] &&
    data.choices[0].message &&
    data.choices[0].message.content;

  if (!answer) {
    throw new Error('The API returned no assistant content.');
  }

  return answer;
}

The code reads all selected prompts at once and writes all answers with one setValues call. It still makes one model request per non-empty row; the batch write reduces Sheets operations, not inference calls.

Why a menu action is safer than =AI(A2)

Custom functions can recalculate when referenced cells change, when the sheet opens, or when formulas are copied. A visible formula is also easy to fill down thousands of rows. A menu action makes the user choose a bounded range and run it intentionally.

PatternBenefitRisk
Custom formulaFamiliar spreadsheet UXRecalculation can repeat calls and cost
Menu actionExplicit range and timingRequires authorization and a click
Installable triggerAutomated schedule or eventRuns as its creator and can fail out of view
External workerStrong queues, retries and observabilityMore infrastructure

For small editorial or classification batches, the menu pattern is a practical default. For large or regulated workloads, send jobs to a controlled backend rather than turning the spreadsheet into the execution engine.

Quotas and cost controls

Google's published Apps Script quotas are per user and can change. The current documentation lists daily URL Fetch limits and per-call size limits; a model provider has separate rate and billing limits. You must satisfy both systems.

Add these controls before letting a team use the sheet:

  • cap the number of selected rows per run;
  • skip rows whose answer column is already populated;
  • add a confirmation dialog for large selections;
  • record model ID and processed time in separate columns;
  • use a document lock to prevent two users from running the same range concurrently;
  • truncate or reject unexpectedly long prompts;
  • keep sensitive data out of prompts unless policy permits the external request;
  • review usage logs after the first real batch.

The exact google sheets openai api query is small, while the broader google sheets ai topic is larger and more competitive. One comprehensive owner page should therefore cover secret storage, UrlFetchApp, batching, quotas and repeat-call prevention instead of creating separate thin pages for every phrasing.

Troubleshooting

Authorization prompt appears: UrlFetchApp requires permission to make external requests. Review the requested scope and authorize only the intended script project.

401 response: verify the Script Property name and that the value is an inference key. Do not read the key from a spreadsheet cell.

404 response: confirm the URL contains /v1/chat/completions exactly once.

Model not found: copy the current ID from the LumeAPI model catalog.

Too many calls: reduce the selected range, skip completed rows and wait for quota or rate-limit recovery. Do not immediately rerun the whole batch.

Answers overwrite data: keep prompts in column A and reserve column B before running, or change the output column in the script.

Sources and testing boundary

The JavaScript was syntax-reviewed on August 3, 2026. No customer inference key or live Google Sheet was available in this editorial workspace, so run the script on a small test range and inspect both the Apps Script execution log and LumeAPI usage record before wider use.

Ready to call these models?

Create a LumeAPI key in under a minute — one OpenAI-compatible gateway for GPT, Claude, Gemini, and more.