# How `pull_available_models.py` Automatically Fetches Live Model Data and Updates the README

> Learn how pull_available_models.py automatically fetches live model data from LLM APIs and updates your README file, ensuring accurate and up-to-date information.

- 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 [`pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/pull_available_models.py) script queries multiple LLM provider APIs, normalizes their model metadata into markdown lists, and injects these into a template to regenerate the repository's [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md) automatically.**

The `free-llm-api-resources` repository tracks free LLM API endpoints and their available models. The [`pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/pull_available_models.py) script functions as a self-contained data pipeline that fetches current model information from providers like **Groq**, **OpenRouter**, and **Cloudflare**, then reconstructs the project documentation without manual editing.

## Loading Configuration and Initialization

The script establishes its execution context by loading environment variables via `dotenv` and determining its directory through the `script_dir` variable. This allows it to locate the template and target files using relative paths according to `src/pull_available_models.py#L22-L24`.

A dedicated logger is instantiated through the `create_logger()` function to emit debug information during execution. This logging infrastructure, implemented at `src/pull_available_models.py#L26-L33`, monitors each provider fetch operation for troubleshooting and verification.

## Fetching Live Model Data from Provider APIs

For each supported provider, the script implements a specific `fetch_*_models(logger)` function (such as `fetch_groq_models()`, `fetch_kluster_models()`, and `fetch_openrouter_models()`). These functions issue HTTPS `GET` or `POST` requests using the `requests` library to query public model catalog endpoints.

The fetchers return standardized lists of dictionaries containing model metadata such as `{"id": "...", "name": "...", "limits": {...}}`. Each function optionally enriches the response with rate-limit data specific to that provider. The implementation pattern for Groq, which demonstrates API querying and data enrichment, appears at `src/pull_available_models.py#L115-L148`.

## Aggregating and Normalizing Model Information

Within the `main()` function, the script calls every provider-specific fetch routine and concatenates their results. It sorts the aggregated models alphabetically to ensure consistent ordering, then constructs two distinct markdown sections:

- **Full model list** (`model_list_markdown`): Contains all available models across providers
- **Trial-only list** (`trial_list_markdown`): Filters for models available on free tiers

This aggregation logic appears in the tail section of [`src/pull_available_models.py`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/pull_available_models.py), where the script prepares the content for template injection.

## Template Injection and README Generation

Rather than building the README from scratch, the script uses a static template located at [`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md). This template contains specific placeholders that the script replaces with generated content:

- `{{MODEL_LIST}}`: Injection point for the complete model catalog
- `{{TRIAL_LIST_MARKDOWN}}`: Insertion location for trial-tier models  
- `{{TOC}}`: Placeholder for the dynamically generated table of contents

The replacement operation occurs at `src/pull_available_models.py#L53-L63`, where the script loads the template, prepends an optional warning message, and substitutes the markdown lists into the document structure.

## Table of Contents Generation and Final Output

After injecting the model lists, the script calls `generate_toc()` to create a markdown table of contents based on the document headers. It replaces the `{{TOC}}` placeholder with this generated navigation element.

The final step writes the processed content to the repository root. The script outputs to [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md) (one directory up from `src/`) as shown at `src/pull_available_models.py#L65-L67`, with a confirmation log line signaling successful completion.

## CI/CD Automation

The automation executes through GitHub Actions configured in [`.github/workflows/update-readme.yml`](https://github.com/cheahjs/free-llm-api-resources/blob/main/.github/workflows/update-readme.yml). The workflow triggers on every push to the repository and runs the script with the command `python -u src/pull_available_models.py` at line 43 of the workflow file, ensuring the documentation remains synchronized with provider APIs without manual intervention.

## Summary

- **Environment setup** uses `dotenv` and dynamic `script_dir` resolution to prepare the execution context according to `src/pull_available_models.py#L22-L24`
- **Provider fetchers** implement dedicated functions like `fetch_groq_models()` that query live APIs and return normalized metadata with rate limits
- **Data aggregation** combines results alphabetically into `model_list_markdown` and `trial_list_markdown` strings within the `main()` function
- **Template processing** injects content into [`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md) via placeholder replacement for `{{MODEL_LIST}}`, `{{TRIAL_LIST_MARKDOWN}}`, and `{{TOC}}` at `src/pull_available_models.py#L53-L63`
- **Continuous integration** runs the pipeline automatically on every push via `.github/workflows/update-readme.yml#L43`

## Frequently Asked Questions

### Which LLM providers does the script support?

The script supports **Groq**, **Kluster**, **OpenRouter**, **Cloudflare**, **Hyperbolic**, and other providers listed in the `main()` function. Each provider has a dedicated `fetch_*_models()` function that handles its specific API endpoint, normalizing responses into dictionaries containing `id`, `name`, and `limits` fields.

### How does the script update the README.md file?

The script reads [`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md), replaces the placeholders `{{MODEL_LIST}}`, `{{TRIAL_LIST_MARKDOWN}}`, and `{{TOC}}` with generated markdown content, and writes the result to the repository root as [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md) according to `src/pull_available_models.py#L65-L67`.

### Can I run the update script locally for testing?

Yes. Execute the script from the repository directory using:

```bash
python -u src/pull_available_models.py

```

The script loads local environment variables via `dotenv`, fetches live data from all configured providers, and regenerates the root [`README.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/README.md) file with freshly aggregated model information.

### Where does the README template reside?

The static template lives at **[`src/README_template.md`](https://github.com/cheahjs/free-llm-api-resources/blob/main/src/README_template.md)** relative to the repository root. This file contains the structural markdown and placeholder strings (`{{MODEL_LIST}}`, `{{TRIAL_LIST_MARKDOWN}}`, `{{TOC}}`) that the script populates with current provider data.