Experiment record
Why Open-Meteo returned 429 only from an Apps Script time-driven trigger
A controlled comparison showed the same Apps Script function returning HTTP 200 manually but HTTP 429 from a time-driven trigger, with different observed egress paths. The operational workaround was to relay the request through Cloudflare Workers.
Conclusion
The failure was not caused by a separate scheduled-execution code path in the collector. The same collectOpenMeteoHourly() function succeeded when launched manually but failed with HTTP 429 when launched by a Google Apps Script time-driven trigger.
Temporary diagnostics then showed that the two executions did not present the same outward network path: the successful manual run exposed an IPv6 address to an external IP-check service, while the failing scheduled run exposed a different IPv4 address. The scheduled Open-Meteo request returned a daily API request-limit error.
This does not prove the exact key Open-Meteo uses for rate limiting. It does show that execution context and network path differed while the application code path remained effectively the same. The operational mitigation was to stop calling Open-Meteo directly from Apps Script and instead relay the request through a Cloudflare Worker. The operator reports that this resolved the scheduled-collection problem.
Initial symptom
The weather collector used Google Apps Script to request Open-Meteo data and append missing hourly records to a spreadsheet. Manual execution repeatedly succeeded, while scheduled execution failed.
The relevant scheduled-run response was:
executionType=TRIGGER
OpenMeteo HTTP status=429
Daily API request limit exceeded. Please try again tomorrow.
A manual run during the same investigation returned:
executionType=MANUAL
OpenMeteo HTTP status=200
Exact IP addresses, coordinates, spreadsheet IDs, account identifiers, and collected household data are intentionally omitted from this public record.
Code-path inspection
The collector was inspected before changing architecture.
The important result was negative evidence: there was no trigger-only branch that made an additional Open-Meteo request. Both manual and scheduled execution entered the same collector and the same fetch function.
The HTTP fetch logic also stopped immediately on status 429 rather than continuing exponential retries:
if (status === 429) {
const err = new Error('Open-Meteo HTTP 429');
err.openMeteoRateLimited = true;
throw err;
}
// Later in the retry loop:
if (err && err.openMeteoRateLimited) break;
Therefore, the scheduled job was not exhausting its own allowance by issuing four rapid retries after the first 429.
Diagnostic instrumentation
The collector was temporarily changed to accept the trigger event object and label each execution:
function collectOpenMeteoHourly(e) {
const executionType =
e && e.triggerUid ? 'TRIGGER' : 'MANUAL';
console.log('executionType=' + executionType);
console.log('triggerUid=' +
(e && e.triggerUid ? e.triggerUid : ''));
}
A temporary external IP check was also added with UrlFetchApp.fetch() so that the observed outward address could be logged for each execution. The Open-Meteo HTTP status was logged at the same time.
Controlled comparison
The observed result was:
| Execution | Observed outward path | Open-Meteo result |
|---|---|---|
| Manual editor run | IPv6 | HTTP 200 |
| Time-driven trigger | Different IPv4 path | HTTP 429, daily request limit |
The exact addresses are withheld because they are not required to reproduce the reasoning and may identify infrastructure used by the private workflow.
What this establishes
Verified
- Manual and scheduled executions ran the same collection function.
- The manual execution returned HTTP 200.
- The scheduled execution returned HTTP 429.
- The scheduled error body identified a daily API request limit.
- A temporary external-IP diagnostic saw different outward addresses for the two execution modes.
- The collector did not contain a trigger-only extra Open-Meteo request.
- The 429 path stopped retrying immediately.
Inference
The evidence strongly indicates that the failure depended on execution/network context rather than on different application logic.
A plausible explanation is that the scheduled execution reached Open-Meteo through a network identity or route that was already rate-limited, while the manual execution used a different route. That interpretation is consistent with the observations, but this experiment does not establish Open-Meteo’s exact rate-limit key.
Still unknown
- Whether Open-Meteo keys this limit directly by source IP, a larger network identity, or another provider-side mechanism.
- Whether the temporary IP-check endpoint and Open-Meteo always observe exactly the same egress address.
- The exact post-mitigation scheduled-run log is not retained in this public record.
Mitigation
The direct path:
Apps Script -> Open-Meteo
was replaced operationally with:
Apps Script -> Cloudflare Worker -> Open-Meteo
This removes dependence on the particular Google Apps Script outward route used by the scheduled invocation. The existing data-management behaviour was kept: timestamp-based deduplication, append-only insertion of missing records, and a bounded 72-hour backfill window.
After diagnosis, the temporary external-IP logging was no longer required and should be removed from production code.
Reusable troubleshooting procedure
For any external API that works manually but fails only in a scheduler:
- Confirm that manual and scheduled execution really call the same function and revision.
- Log the execution type explicitly.
- Log the provider HTTP status and a short response body.
- Check whether the scheduled path makes extra calls or retries differently.
- Temporarily compare outward network identity or route if the platform uses shared outbound infrastructure.
- Separate verified evidence from assumptions about the provider’s rate-limit implementation.
- If the execution platform’s egress path is the operational problem, place a controlled relay between the scheduler and the provider.
- Remove diagnostic network calls after the problem is isolated.
Related experiment
This investigation follows the earlier redesign recorded as OPENMETEO-MSM-001, where the weather-history workflow was moved away from a spreadsheet formula and toward direct API collection. The present record documents a later scheduler-specific failure mode in that direct-API design.