# How GitHub Actions Automate README Updates When Model Data Changes in free-llm-api-resources

> Learn how GitHub Actions automatically update your README.md when model data shifts, ensuring accuracy and preventing manual errors in cheahjs/free-llm-api-resources.

- Repository: [Jun Siang Cheah/free-llm-api-resources](https://github.com/cheahjs/free-llm-api-resources)
- Tags: how-to-guide
- Published: 2026-05-07

---

**The repository uses two GitHub Actions workflows—`Update README` and `README Change Validator`—to automatically regenerate the public [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md) from a template whenever model data changes, while blocking direct manual edits to ensure data integrity.**

The `cheahjs/free-llm-api-resources` repository maintains a curated list of free large language model (LLM) APIs that changes frequently as providers update their offerings. To keep the documentation current without manual intervention, the project employs a fully automated pipeline that fetches live model data, renders a new README from a template, and validates that all changes originate from the automation scripts.

## The Automation Pipeline Overview

The system relies on a pair of coordinated workflows located in `.github/workflows/`. The **Update README** workflow handles data collection and file generation, while the **README Change Validator** enforces governance by preventing direct modifications to [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md). Together, they ensure the public documentation always reflects the latest free-tier model availability from providers like Groq, OpenRouter, and Cloudflare.

## The Update README Workflow

Located at [`.github/workflows/update-readme.yml`](https://github.com/cheahjs/free-llm-api-resources/blob/main/.github/workflows/update-readme.yml), this workflow orchestrates the data gathering and regeneration process. It runs on a scheduled cron job and can be triggered manually via `workflow_dispatch`.

### Scheduling and Permissions

The workflow triggers automatically at midnight UTC (`cron: "0 0 * * *"`) or on-demand. It requires specific permissions to write contents, create pull requests, and authenticate with external identity providers:

```yaml
permissions:
  contents: write
  pull-requests: write
  id-token: write

```

### Google Cloud Authentication for Gemini Quotas

To fetch accurate quota data for Google's Gemini models, the workflow authenticates using Workload Identity Federation via `google-github-actions/auth@v3`. This exchanges the GitHub OIDC token for a short-lived service-account JSON file without storing long-lived secrets:

```yaml
- id: auth
  uses: google-github-actions/auth@v3
  with:
    workload_identity_provider: "projects/576328904266/locations/global/workloadIdentityPools/github/providers/cheahjs-org"
    project_id: ${{ secrets.GCP_PROJECT }}

```

### Data Collection and Script Execution

After installing Python 3.12 and dependencies from [`src/requirements.txt`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/requirements.txt), the workflow executes [`src/pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/pull_available_models.py). This script contains multiple `fetch_*_models` functions that query each provider's public API to retrieve model names, IDs, and rate limits. The script constructs two markdown strings: `model_list_markdown` for free providers and `trial_list_markdown` for trial-credit providers.

The script then reads [`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md), replaces placeholder tags with the generated content, and writes the result to the repository root:

```python
with open(os.path.join(script_dir, "README_template.md")) as f:
    readme = f.read()
initial_templated = (
    (warning + readme)
    .replace("{{MODEL_LIST}}", model_list_markdown)
    .replace("{{TRIAL_LIST_MARKDOWN}}", trial_list_markdown)
)
toc_markdown = generate_toc(initial_templated)
with open(os.path.join(script_dir, "..", "README.md"), "w") as f:
    f.write(initial_templated.replace("{{TOC}}", toc_markdown))

```

### Pull Request Creation and Cleanup

After removing the temporary Google credentials file (`rm ${{ steps.auth.outputs.credentials_file_path }}`), the workflow uses `peter-evans/create-pull-request@v8` to open a PR titled *"Update README with latest models"* on the `update-readme` branch, containing only the regenerated [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md).

## The README Change Validator Workflow

Located at [`.github/workflows/readme-change-validator.yml`](https://github.com/cheahjs/free-llm-api-resources/blob/main/.github/workflows/readme-change-validator.yml), this workflow prevents contributors from manually editing [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md), ensuring all changes flow through the automation pipeline. It triggers on pull requests that modify [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md), [`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md), or [`src/pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/pull_available_models.py).

### Enforcing Script-Driven Updates

The validator performs a `git diff` between the PR base and head to count changes to each relevant file. If [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md) changes without corresponding changes to the template or Python script, the workflow fails with exit code 1 and posts an error comment:

```yaml
- name: Validate README changes
  run: |
    if [ "${{ steps.changed-files.outputs.readme_changed }}" -gt 0 ] && \
       [ "${{ steps.changed-files.outputs.template_changed }}" -eq 0 ] && \
       [ "${{ steps.changed-files.outputs.script_changed }}" -eq 0 ]; then
      echo "Error: README.md was modified without corresponding changes in src/README_template.md or src/pull_available_models.py"
      exit 1
    fi

```

This enforcement guarantees that any updates to the public-facing documentation originate from [`src/pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/pull_available_models.py) and [`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md), maintaining a single source of truth for the model data.

## Template and Script Architecture

The automation relies on two key files in the `src/` directory that separate data collection from presentation.

### The README Template Structure

[`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md) contains static content and placeholder tokens that the script replaces at runtime:
- **`{{MODEL_LIST}}`** — Injections the generated markdown for free providers
- **`{{TRIAL_LIST_MARKDOWN}}`** — Injects the generated markdown for trial-credit providers  
- **`{{TOC}}`** — Injects the auto-generated table of contents created by `generate_toc()`

This templating approach allows maintainers to modify static sections of the README without touching the Python code that handles API responses.

### Data Aggregation Logic

[`src/pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/pull_available_models.py) functions as the extraction engine. Each provider (Groq, OpenRouter, Cloudflare Workers AI, etc.) has a dedicated fetch function that returns standardized model metadata. The script aggregates these into markdown lists, handles API authentication using repository secrets like `GROQ_API_KEY`, and manages rate-limit documentation for each endpoint.

## Summary

- **Dual-workflow architecture**: The `Update README` workflow ([`update-readme.yml`](https://github.com/cheahjs/free-llm-api-resources/blob/main/update-readme.yml)) generates content, while the `README Change Validator` ([`readme-change-validator.yml`](https://github.com/cheahjs/free-llm-api-resources/blob/main/readme-change-validator.yml)) enforces governance.
- **Template-driven generation**: [`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md) uses placeholders (`{{MODEL_LIST}}`, `{{TRIAL_LIST_MARKDOWN}}`, `{{TOC}}`) that [`src/pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/pull_available_models.py) populates with live API data.
- **Secure credential handling**: Google Cloud authentication uses Workload Identity Federation to access Gemini quota data without persistent secrets.
- **Immutable public README**: Direct edits to [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md) are blocked unless accompanied by changes to the template or generator script, ensuring data consistency.

## Frequently Asked Questions

### How often does the README automatically update?

The `Update README` workflow runs on a cron schedule of `0 0 * * *`, meaning it executes automatically at midnight UTC every day. It can also be triggered manually via the GitHub Actions interface using `workflow_dispatch` for immediate updates when urgent provider changes occur.

### What happens if someone tries to edit the README.md file directly?

The `README Change Validator` workflow detects direct modifications by checking if [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md) appears in the pull request diff without corresponding changes to [`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md) or [`src/pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/pull_available_models.py). If this condition is met, the workflow fails and blocks the merge, posting a comment instructing the contributor to modify the template or script instead.

### Why does the workflow need Google Cloud authentication?

The script queries Google's Cloud Quotas API to retrieve current rate limits and availability for Gemini models, which requires authenticated access. The workflow uses Workload Identity Federation to securely exchange the GitHub Actions OIDC token for temporary Google Cloud credentials, avoiding the storage of long-lived service account keys in repository secrets.

### Can the automation handle new LLM providers?

Yes. To add a new provider, you would extend [`src/pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/pull_available_models.py) by adding a new `fetch_*_models` function that calls the provider's API and returns the standardized model metadata format. The script will automatically include the new data in the `model_list_markdown` or `trial_list_markdown` sections during the next scheduled run.