diff --git a/api-template.md b/api-template.md
new file mode 100644
index 0000000..e22c05c
--- /dev/null
+++ b/api-template.md
@@ -0,0 +1,106 @@
+# Replicate Python SDK API reference
+
+## Installation
+
+```bash
+pip install replicate
+```
+
+## Initialize a client
+
+Start by setting a `REPLICATE_API_TOKEN` environment variable in your environment. You can create a token at [replicate.com/account/api-tokens](https://replicate.com/account/api-tokens).
+
+Then use this code to initialize a client:
+
+```py
+import replicate
+```
+
+That's it! You can now use the client to make API calls.
+
+
+If you want to explicitly pass the token when creating a client, you can do so like this:
+
+
+```python
+import os
+import replicate
+
+client = replicate.Replicate(
+ bearer_token=os.environ["REPLICATE_API_TOKEN"]
+)
+```
+
+## High-level operations
+
+### `replicate.use()`
+
+Create a reference to a model that can be used to make predictions.
+
+```python
+import replicate
+
+claude = replicate.use("anthropic/claude-sonnet-4")
+
+output = claude(prompt="Hello, world!")
+print(output)
+
+banana = replicate.use("google/nano-banana")
+output = banana(prompt="Make me a sandwich")
+print(output)
+```
+
+Note: The `replicate.use()` method only returns output. If you need access to more metadata like prediction ID, status, metrics, or input values, use `replicate.predictions.create()` instead.
+
+### `replicate.run()`
+
+Run a model and wait for the output. This is a convenience method that creates a prediction and waits for it to complete.
+
+```python
+import replicate
+
+# Run a model and get the output directly
+output = replicate.run(
+ "anthropic/claude-sonnet-4",
+ input={"prompt": "Hello, world!"}
+)
+print(output)
+```
+
+Note: The `replicate.run()` method only returns output. If you need access to more metadata like prediction ID, status, metrics, or input values, use `replicate.predictions.create()` instead.
+
+
+## API operations
+
+
+
+## Low-level API
+
+For cases where you need to make direct API calls not covered by the SDK methods, you can use the low-level request interface:
+
+### Making custom requests
+
+```python
+import replicate
+
+client = replicate.Replicate()
+
+# Make a custom GET request
+response = client.get("/custom/endpoint")
+
+# Make a custom POST request with data
+response = client.post(
+ "/custom/endpoint",
+ json={"key": "value"}
+)
+
+# Make a custom request with all options
+response = client.request(
+ method="PATCH",
+ url="/custom/endpoint",
+ json={"key": "value"},
+ headers={"X-Custom-Header": "value"}
+)
+```
+
+See the [README](https://github.com/replicate/replicate-python-stainless/blob/main/README.md) for more details about response handing, error handling, pagination, async support, and more.
diff --git a/api.md b/api.md
index d037762..c6c966f 100644
--- a/api.md
+++ b/api.md
@@ -1,193 +1,731 @@
-# Replicate
+# Replicate Python SDK API reference
+
+## Installation
+
+```bash
+pip install replicate
+```
+
+## Initialize a client
+
+Start by setting a `REPLICATE_API_TOKEN` environment variable in your environment. You can create a token at [replicate.com/account/api-tokens](https://replicate.com/account/api-tokens).
+
+Then use this code to initialize a client:
+
+```py
+import replicate
+```
+
+That's it! You can now use the client to make API calls.
+
+
+If you want to explicitly pass the token when creating a client, you can do so like this:
+
+
+```python
+import os
+import replicate
+
+client = replicate.Replicate(
+ bearer_token=os.environ["REPLICATE_API_TOKEN"]
+)
+```
+
+## High-level operations
+
+### `replicate.use()`
+
+Create a reference to a model that can be used to make predictions.
+
+```python
+import replicate
+
+claude = replicate.use("anthropic/claude-sonnet-4")
+
+output = claude(prompt="Hello, world!")
+print(output)
+
+banana = replicate.use("google/nano-banana")
+output = banana(prompt="Make me a sandwich")
+print(output)
+```
+
+Note: The `replicate.use()` method only returns output. If you need access to more metadata like prediction ID, status, metrics, or input values, use `replicate.predictions.create()` instead.
+
+### `replicate.run()`
+
+Run a model and wait for the output. This is a convenience method that creates a prediction and waits for it to complete.
+
+```python
+import replicate
+
+# Run a model and get the output directly
+output = replicate.run(
+ "anthropic/claude-sonnet-4",
+ input={"prompt": "Hello, world!"}
+)
+print(output)
+```
+
+Note: The `replicate.run()` method only returns output. If you need access to more metadata like prediction ID, status, metrics, or input values, use `replicate.predictions.create()` instead.
+
+
+## API operations
+
+Available operations:
+
+- [`search`](#search)
+- [`predictions.create`](#predictionscreate)
+- [`predictions.get`](#predictionsget)
+- [`predictions.list`](#predictionslist)
+- [`predictions.cancel`](#predictionscancel)
+- [`models.create`](#modelscreate)
+- [`models.get`](#modelsget)
+- [`models.list`](#modelslist)
+- [`models.delete`](#modelsdelete)
+- [`models.examples.list`](#modelsexampleslist)
+- [`models.predictions.create`](#modelspredictionscreate)
+- [`models.readme.get`](#modelsreadmeget)
+- [`models.versions.get`](#modelsversionsget)
+- [`models.versions.list`](#modelsversionslist)
+- [`models.versions.delete`](#modelsversionsdelete)
+- [`collections.get`](#collectionsget)
+- [`collections.list`](#collectionslist)
+- [`deployments.create`](#deploymentscreate)
+- [`deployments.get`](#deploymentsget)
+- [`deployments.list`](#deploymentslist)
+- [`deployments.update`](#deploymentsupdate)
+- [`deployments.delete`](#deploymentsdelete)
+- [`deployments.predictions.create`](#deploymentspredictionscreate)
+- [`files.list`](#fileslist)
+- [`files.create`](#filescreate)
+- [`files.delete`](#filesdelete)
+- [`files.get`](#filesget)
+- [`files.download`](#filesdownload)
+- [`trainings.create`](#trainingscreate)
+- [`trainings.get`](#trainingsget)
+- [`trainings.list`](#trainingslist)
+- [`trainings.cancel`](#trainingscancel)
+- [`hardware.list`](#hardwarelist)
+- [`account.get`](#accountget)
+- [`webhooks.default.secret.get`](#webhooksdefaultsecretget)
+
+### `search`
+
+Search models, collections, and docs (beta)
+
+
+```python
+response = replicate.search(
+ query="nano banana",
+)
+print(response.collections)
+```
+
+Docs: https://replicate.com/docs/reference/http#search
+
+---
+
+### `predictions.create`
+
+Create a prediction
+
+
+```python
+prediction = replicate.predictions.create(
+ input={
+ "text": "Alice"
+ },
+ version="replicate/hello-world:9dcd6d78e7c6560c340d916fe32e9f24aabfa331e5cce95fe31f77fb03121426",
+)
+print(prediction.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#predictions.create
+
+---
+
+### `predictions.get`
+
+Get a prediction
+
+
+```python
+prediction = replicate.predictions.get(
+ prediction_id="prediction_id",
+)
+print(prediction.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#predictions.get
+
+---
+
+### `predictions.list`
+
+List predictions
+
+
+```python
+page = replicate.predictions.list()
+page = page.results[0]
+print(page.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#predictions.list
+
+---
+
+### `predictions.cancel`
+
+Cancel a prediction
+
+
+```python
+prediction = replicate.predictions.cancel(
+ prediction_id="prediction_id",
+)
+print(prediction.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#predictions.cancel
+
+---
+
+### `models.create`
+
+Create a model
+
+
+```python
+model = replicate.models.create(
+ hardware="cpu",
+ name="hot-dog-detector",
+ owner="alice",
+ visibility="public",
+)
+print(model.cover_image_url)
+```
+
+Docs: https://replicate.com/docs/reference/http#models.create
+
+---
+
+### `models.get`
+
+Get a model
+
+
+```python
+model = replicate.models.get(
+ model_owner="model_owner",
+ model_name="model_name",
+)
+print(model.cover_image_url)
+```
+
+Docs: https://replicate.com/docs/reference/http#models.get
+
+---
+
+### `models.list`
+
+List public models
+
+
+```python
+page = replicate.models.list()
+page = page.results[0]
+print(page.cover_image_url)
+```
+
+Docs: https://replicate.com/docs/reference/http#models.list
+
+---
+
+### `models.delete`
+
+Delete a model
+
+
+```python
+replicate.models.delete(
+ model_owner="model_owner",
+ model_name="model_name",
+)
+```
+
+Docs: https://replicate.com/docs/reference/http#models.delete
+
+---
+
+### `models.examples.list`
+
+List examples for a model
+
+
+```python
+page = replicate.models.examples.list(
+ model_owner="model_owner",
+ model_name="model_name",
+)
+page = page.results[0]
+print(page.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#models.examples.list
+
+---
+
+### `models.predictions.create`
+
+Create a prediction using an official model
+
+
+```python
+prediction = replicate.models.predictions.create(
+ model_owner="model_owner",
+ model_name="model_name",
+ input={
+ "prompt": "Tell me a joke",
+ "system_prompt": "You are a helpful assistant",
+ },
+)
+print(prediction.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#models.predictions.create
+
+---
+
+### `models.readme.get`
+
+Get a model's README
-Types:
```python
-from replicate.types import SearchResponse
+readme = replicate.models.readme.get(
+ model_owner="model_owner",
+ model_name="model_name",
+)
+print(readme)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#models.readme.get
+
+---
-- replicate.search(\*\*params) -> SearchResponse
+### `models.versions.get`
-# Collections
+Get a model version
-Types:
```python
-from replicate.types import CollectionListResponse, CollectionGetResponse
+version = replicate.models.versions.get(
+ model_owner="model_owner",
+ model_name="model_name",
+ version_id="version_id",
+)
+print(version.id)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#models.versions.get
+
+---
-- replicate.collections.list() -> SyncCursorURLPage[CollectionListResponse]
-- replicate.collections.get(\*, collection_slug) -> CollectionGetResponse
+### `models.versions.list`
-# Deployments
+List model versions
-Types:
```python
-from replicate.types import (
- DeploymentCreateResponse,
- DeploymentUpdateResponse,
- DeploymentListResponse,
- DeploymentGetResponse,
+page = replicate.models.versions.list(
+ model_owner="model_owner",
+ model_name="model_name",
)
+page = page.results[0]
+print(page.id)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#models.versions.list
-- replicate.deployments.create(\*\*params) -> DeploymentCreateResponse
-- replicate.deployments.update(\*, deployment_owner, deployment_name, \*\*params) -> DeploymentUpdateResponse
-- replicate.deployments.list() -> SyncCursorURLPage[DeploymentListResponse]
-- replicate.deployments.delete(\*, deployment_owner, deployment_name) -> None
-- replicate.deployments.get(\*, deployment_owner, deployment_name) -> DeploymentGetResponse
+---
-## Predictions
+### `models.versions.delete`
-Methods:
+Delete a model version
-- replicate.deployments.predictions.create(\*, deployment_owner, deployment_name, \*\*params) -> Prediction
-# Hardware
+```python
+replicate.models.versions.delete(
+ model_owner="model_owner",
+ model_name="model_name",
+ version_id="version_id",
+)
+```
+
+Docs: https://replicate.com/docs/reference/http#models.versions.delete
+
+---
+
+### `collections.get`
+
+Get a collection of models
-Types:
```python
-from replicate.types import HardwareListResponse
+collection = replicate.collections.get(
+ collection_slug="collection_slug",
+)
+print(collection.description)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#collections.get
-- replicate.hardware.list() -> HardwareListResponse
+---
-# Account
+### `collections.list`
+
+List collections of models
-Types:
```python
-from replicate.types import AccountGetResponse
+page = replicate.collections.list()
+page = page.results[0]
+print(page.description)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#collections.list
+
+---
-- replicate.account.get() -> AccountGetResponse
+### `deployments.create`
-# Models
+Create a deployment
-Types:
```python
-from replicate.types import (
- ModelCreateResponse,
- ModelListResponse,
- ModelGetResponse,
- ModelSearchResponse,
+deployment = replicate.deployments.create(
+ hardware="hardware",
+ max_instances=0,
+ min_instances=0,
+ model="model",
+ name="name",
+ version="version",
)
+print(deployment.current_release)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#deployments.create
-- replicate.models.create(\*\*params) -> ModelCreateResponse
-- replicate.models.list() -> SyncCursorURLPage[ModelListResponse]
-- replicate.models.delete(\*, model_owner, model_name) -> None
-- replicate.models.get(\*, model_owner, model_name) -> ModelGetResponse
-- replicate.models.search(\*\*params) -> SyncCursorURLPage[ModelSearchResponse]
+---
-## Examples
+### `deployments.get`
-Methods:
+Get a deployment
-- replicate.models.examples.list(\*, model_owner, model_name) -> SyncCursorURLPage[Prediction]
-## Predictions
+```python
+deployment = replicate.deployments.get(
+ deployment_owner="deployment_owner",
+ deployment_name="deployment_name",
+)
+print(deployment.current_release)
+```
+
+Docs: https://replicate.com/docs/reference/http#deployments.get
-Methods:
+---
-- replicate.models.predictions.create(\*, model_owner, model_name, \*\*params) -> Prediction
+### `deployments.list`
-## Readme
+List deployments
-Types:
```python
-from replicate.types.models import ReadmeGetResponse
+page = replicate.deployments.list()
+page = page.results[0]
+print(page.current_release)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#deployments.list
-- replicate.models.readme.get(\*, model_owner, model_name) -> str
+---
-## Versions
+### `deployments.update`
+
+Update a deployment
-Types:
```python
-from replicate.types.models import VersionListResponse, VersionGetResponse
+deployment = replicate.deployments.update(
+ deployment_owner="deployment_owner",
+ deployment_name="deployment_name",
+)
+print(deployment.current_release)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#deployments.update
-- replicate.models.versions.list(\*, model_owner, model_name) -> SyncCursorURLPage[VersionListResponse]
-- replicate.models.versions.delete(\*, model_owner, model_name, version_id) -> None
-- replicate.models.versions.get(\*, model_owner, model_name, version_id) -> VersionGetResponse
+---
-# Predictions
+### `deployments.delete`
+
+Delete a deployment
-Types:
```python
-from replicate.types import Prediction, PredictionOutput, PredictionRequest
+replicate.deployments.delete(
+ deployment_owner="deployment_owner",
+ deployment_name="deployment_name",
+)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#deployments.delete
-- replicate.predictions.create(\*\*params) -> Prediction
-- replicate.predictions.list(\*\*params) -> SyncCursorURLPageWithCreatedFilters[Prediction]
-- replicate.predictions.cancel(\*, prediction_id) -> Prediction
-- replicate.predictions.get(\*, prediction_id) -> Prediction
+---
-# Trainings
+### `deployments.predictions.create`
+
+Create a prediction using a deployment
-Types:
```python
-from replicate.types import (
- TrainingCreateResponse,
- TrainingListResponse,
- TrainingCancelResponse,
- TrainingGetResponse,
+prediction = replicate.deployments.predictions.create(
+ deployment_owner="deployment_owner",
+ deployment_name="deployment_name",
+ input={
+ "prompt": "Tell me a joke",
+ "system_prompt": "You are a helpful assistant",
+ },
)
+print(prediction.id)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#deployments.predictions.create
+
+---
+
+### `files.list`
+
+List files
+
+
+```python
+page = replicate.files.list()
+page = page.results[0]
+print(page.id)
+```
-- replicate.trainings.create(\*, model_owner, model_name, version_id, \*\*params) -> TrainingCreateResponse
-- replicate.trainings.list() -> SyncCursorURLPage[TrainingListResponse]
-- replicate.trainings.cancel(\*, training_id) -> TrainingCancelResponse
-- replicate.trainings.get(\*, training_id) -> TrainingGetResponse
+Docs: https://replicate.com/docs/reference/http#files.list
-# Webhooks
+---
-## Default
+### `files.create`
-### Secret
+Create a file
-Types:
```python
-from replicate.types.webhooks.default import SecretGetResponse
+file = replicate.files.create(
+ content=b"raw file contents",
+)
+print(file.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#files.create
+
+---
+
+### `files.delete`
+
+Delete a file
+
+
+```python
+replicate.files.delete(
+ file_id="file_id",
+)
+```
+
+Docs: https://replicate.com/docs/reference/http#files.delete
+
+---
+
+### `files.get`
+
+Get a file
+
+
+```python
+file = replicate.files.get(
+ file_id="file_id",
+)
+print(file.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#files.get
+
+---
+
+### `files.download`
+
+Download a file
+
+
+```python
+response = replicate.files.download(
+ file_id="file_id",
+ expiry=0,
+ owner="owner",
+ signature="signature",
+)
+print(response)
+content = response.read()
+print(content)
```
-Methods:
+Docs: https://replicate.com/docs/reference/http#files.download
-- replicate.webhooks.default.secret.get() -> SecretGetResponse
+---
-# Files
+### `trainings.create`
+
+Create a training
-Types:
```python
-from replicate.types import FileCreateResponse, FileListResponse, FileGetResponse
+training = replicate.trainings.create(
+ model_owner="model_owner",
+ model_name="model_name",
+ version_id="version_id",
+ destination="destination",
+ input={},
+)
+print(training.id)
```
+
+Docs: https://replicate.com/docs/reference/http#trainings.create
+
+---
+
+### `trainings.get`
+
+Get a training
+
+
+```python
+training = replicate.trainings.get(
+ training_id="training_id",
+)
+print(training.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#trainings.get
+
+---
+
+### `trainings.list`
+
+List trainings
+
+
+```python
+page = replicate.trainings.list()
+page = page.results[0]
+print(page.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#trainings.list
+
+---
+
+### `trainings.cancel`
+
+Cancel a training
+
+
+```python
+response = replicate.trainings.cancel(
+ training_id="training_id",
+)
+print(response.id)
+```
+
+Docs: https://replicate.com/docs/reference/http#trainings.cancel
+
+---
+
+### `hardware.list`
+
+List available hardware for models
+
+
+```python
+hardware = replicate.hardware.list()
+print(hardware)
+```
+
+Docs: https://replicate.com/docs/reference/http#hardware.list
+
+---
+
+### `account.get`
+
+Get the authenticated account
+
+
+```python
+account = replicate.account.get()
+print(account.type)
+```
+
+Docs: https://replicate.com/docs/reference/http#account.get
+
+---
+
+### `webhooks.default.secret.get`
+
+Get the signing secret for the default webhook
+
+
+```python
+secret = replicate.webhooks.default.secret.get()
+print(secret.key)
+```
+
+Docs: https://replicate.com/docs/reference/http#webhooks.default.secret.get
+
+---
+
+
+## Low-level API
+
+For cases where you need to make direct API calls not covered by the SDK methods, you can use the low-level request interface:
+
+### Making custom requests
+
+```python
+import replicate
+
+client = replicate.Replicate()
+
+# Make a custom GET request
+response = client.get("/custom/endpoint")
+
+# Make a custom POST request with data
+response = client.post(
+ "/custom/endpoint",
+ json={"key": "value"}
+)
+
+# Make a custom request with all options
+response = client.request(
+ method="PATCH",
+ url="/custom/endpoint",
+ json={"key": "value"},
+ headers={"X-Custom-Header": "value"}
+)
+```
+
+See the [README](https://github.com/replicate/replicate-python-stainless/blob/main/README.md) for more details about response handing, error handling, pagination, async support, and more.
diff --git a/pyproject.toml b/pyproject.toml
index 044be7f..c790881 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -70,6 +70,7 @@ format = { chain = [
]}
"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md"
"format:ruff" = "ruff format"
+"update-api-docs" = "python scripts/utils/update-api-docs.py"
"lint" = { chain = [
"check:ruff",
diff --git a/scripts/update-api-docs b/scripts/update-api-docs
new file mode 100755
index 0000000..d84cd61
--- /dev/null
+++ b/scripts/update-api-docs
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+echo "==> Updating API documentation"
+python3 scripts/utils/update-api-docs.py
\ No newline at end of file
diff --git a/scripts/utils/update-api-docs.py b/scripts/utils/update-api-docs.py
new file mode 100644
index 0000000..acb3538
--- /dev/null
+++ b/scripts/utils/update-api-docs.py
@@ -0,0 +1,315 @@
+#!/usr/bin/env python3
+"""
+Script to generate API.md documentation from OpenAPI spec with code samples.
+"""
+
+from __future__ import annotations
+
+import re
+import sys
+import json
+import tempfile
+import subprocess
+from typing import Any
+from pathlib import Path
+
+
+def download_openapi_spec() -> dict[str, Any]:
+ """Download and parse the OpenAPI spec from Stainless."""
+ import urllib.request
+
+ spec_url = "https://app.stainless.com/api/spec/documented/replicate-client/openapi.documented.yml"
+
+ print(f"Downloading OpenAPI spec from {spec_url}...")
+
+ with urllib.request.urlopen(spec_url) as response:
+ yaml_content = response.read().decode("utf-8")
+
+ # Save to temp file to dereference it
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".yml", delete=False) as f:
+ f.write(yaml_content)
+ temp_path = f.name
+
+ try:
+ # Try to use swagger-cli if available to dereference
+ result = subprocess.run(
+ ["npx", "@apidevtools/swagger-cli", "bundle", temp_path, "--dereference", "--type", "json"],
+ capture_output=True,
+ text=True,
+ )
+ if result.returncode == 0:
+ spec = json.loads(result.stdout)
+ print("Successfully dereferenced OpenAPI spec using swagger-cli")
+ else:
+ # Fallback to PyYAML
+ import yaml
+
+ with open(temp_path, "r") as f:
+ spec = yaml.safe_load(f)
+ print("Loaded OpenAPI spec using PyYAML (not dereferenced)")
+ finally:
+ Path(temp_path).unlink(missing_ok=True)
+
+ return spec
+
+
+def extract_operation_id(path: str, method: str, operation: dict[str, Any]) -> str:
+ """Extract operation ID from operation object."""
+ if "operationId" in operation:
+ return operation["operationId"]
+
+ # Fallback: generate from path and method
+ # Convert path like /models/{model_owner}/{model_name} to models_get
+ clean_path = path.strip("/").replace("{", "").replace("}", "").replace("/", "_")
+ return f"{clean_path}_{method}"
+
+
+def remove_client_instantiation(code: str) -> str:
+ """Remove client instantiation code from examples."""
+ # Pattern to match Replicate client instantiation
+ patterns = [
+ # Multi-line instantiation
+ r"replicate\s*=\s*Replicate\s*\([^)]*\)\s*\n",
+ # Single-line instantiation
+ r"replicate\s*=\s*Replicate\s*\([^)]*\)",
+ # Import and instantiation in one
+ r"from replicate import Replicate\s*\n",
+ r"import replicate\s*\n\s*replicate\s*=\s*Replicate\s*\([^)]*\)\s*\n?",
+ # Client with bearer_token
+ r'client\s*=\s*replicate\.Replicate\s*\(\s*bearer_token\s*=\s*"[^"]*"\s*\)\s*\n?',
+ r'replicate\s*=\s*replicate\.Replicate\s*\(\s*bearer_token\s*=\s*"[^"]*"\s*\)\s*\n?',
+ ]
+
+ cleaned = code
+ for pattern in patterns:
+ cleaned = re.sub(pattern, "", cleaned, flags=re.MULTILINE)
+
+ # Clean up extra blank lines at the start
+ lines = cleaned.split("\n")
+ while lines and not lines[0].strip():
+ lines.pop(0)
+
+ return "\n".join(lines)
+
+
+def format_operation(operation_id: str, operation: dict[str, Any]) -> str:
+ """Format a single operation as markdown."""
+ summary = operation.get("summary", "").strip()
+
+ # Extract code sample
+ code_sample = ""
+ if "x-readme" in operation and "code-samples" in operation["x-readme"]:
+ samples = operation["x-readme"]["code-samples"]
+ # Look for Python sample
+ for sample in samples:
+ if sample.get("language") == "python" or sample.get("lang") == "python":
+ code = sample.get("code", "")
+ code = remove_client_instantiation(code)
+ if code:
+ code_sample = f"\n```python\n{code}\n```"
+ break
+
+ # Generate docs link
+ docs_link = f"https://replicate.com/docs/reference/http#{operation_id}"
+
+ # Build the markdown section
+ lines = [f"### `{operation_id}`", ""]
+
+ if summary:
+ lines.append(summary)
+ lines.append("")
+
+ if code_sample:
+ lines.append(code_sample)
+ lines.append("")
+
+ lines.append(f"Docs: {docs_link}")
+ lines.append("")
+ lines.append("---")
+ lines.append("")
+
+ return "\n".join(lines)
+
+
+def get_ordered_operations(spec: dict[str, Any]) -> list[tuple[str, str, str, dict[str, Any]]]:
+ """Get operations in the specified order."""
+ # Specified order from the Linear issue
+ operation_order = [
+ "search",
+ "predictions.create",
+ "predictions.get",
+ "predictions.list",
+ "predictions.cancel",
+ "models.create",
+ "models.get",
+ "models.list",
+ "models.search",
+ "models.delete",
+ "models.examples.list",
+ "models.predictions.create",
+ "models.readme.get",
+ "models.versions.get",
+ "models.versions.list",
+ "models.versions.delete",
+ "collections.get",
+ "collections.list",
+ "deployments.create",
+ "deployments.get",
+ "deployments.list",
+ "deployments.update",
+ "deployments.delete",
+ "deployments.predictions.create",
+ "files.list",
+ "files.create",
+ "files.delete",
+ "files.get",
+ "files.download",
+ "trainings.create",
+ "trainings.get",
+ "trainings.list",
+ "trainings.cancel",
+ "hardware.list",
+ "account.get",
+ "webhooks.default.secret.get",
+ ]
+
+ operations: dict[str, tuple[str, str, str, dict[str, Any]]] = {}
+
+ # Extract all operations from paths
+ for path, path_obj in spec.get("paths", {}).items():
+ for method in ["get", "post", "put", "patch", "delete"]:
+ if method in path_obj:
+ operation = path_obj[method]
+ op_id = extract_operation_id(path, method, operation)
+
+ # Try to match with our ordered list using multiple strategies
+ matched_name = None
+
+ # Strategy 1: Direct operationId match
+ if op_id in operation_order:
+ matched_name = op_id
+
+ # Strategy 2: Case-insensitive exact match
+ if not matched_name:
+ for ordered_name in operation_order:
+ if op_id.lower() == ordered_name.lower():
+ matched_name = ordered_name
+ break
+
+ # Strategy 3: Convert ordered name to possible operation IDs
+ if not matched_name:
+ for ordered_name in operation_order:
+ # e.g., "predictions.create" might be "predictionsCreate" or "predictions_create"
+ variants = [
+ ordered_name.replace(".", "_"),
+ ordered_name.replace(".", ""),
+ "".join(word.capitalize() if i > 0 else word for i, word in enumerate(ordered_name.split("."))),
+ ]
+
+ if op_id in variants or any(v.lower() == op_id.lower() for v in variants):
+ matched_name = ordered_name
+ break
+
+ # Strategy 4: Match by path structure and method
+ if not matched_name:
+ for ordered_name in operation_order:
+ ordered_parts = ordered_name.split(".")
+ if len(ordered_parts) >= 2:
+ resource = ordered_parts[0]
+ action = ordered_parts[-1]
+
+ # Check various path patterns
+ path_lower = path.lower()
+ if (resource in path_lower and
+ ((action == "create" and method == "post") or
+ (action == "get" and method == "get" and "{" in path) or
+ (action == "list" and method == "get" and "{" not in path) or
+ (action == "update" and method in ["put", "patch"]) or
+ (action == "delete" and method == "delete") or
+ (action == "cancel" and method == "post" and "cancel" in path) or
+ (action == "search" and method == "get" and "search" in path))):
+ matched_name = ordered_name
+ break
+
+ key = matched_name or op_id
+ operations[key] = (op_id, path, method, operation)
+
+ # Order operations according to the specified list
+ ordered = []
+ added_keys = set()
+
+ for name in operation_order:
+ if name in operations:
+ ordered.append(operations[name])
+ added_keys.add(name)
+
+ # Add any remaining operations not in the ordered list
+ for key, value in operations.items():
+ if key not in added_keys:
+ ordered.append(value)
+
+ return ordered
+
+
+def generate_api_docs(spec: dict[str, Any]) -> str:
+ """Generate the API operations documentation."""
+ lines = []
+ operations = get_ordered_operations(spec)
+
+ # Generate sorted list of operations at the top
+ lines.append("Available operations:")
+ lines.append("")
+ for op_id, _path, _method, _operation in operations:
+ # Create anchor link from operation_id
+ anchor = op_id.lower().replace('.', '').replace('_', '')
+ lines.append(f"- [`{op_id}`](#{anchor})")
+ lines.append("")
+
+ # Generate detailed documentation for each operation
+ for op_id, _path, _method, operation in operations:
+ lines.append(format_operation(op_id, operation))
+
+ return "\n".join(lines)
+
+
+def update_api_md():
+ """Main function to update api.md file."""
+ # Paths
+ project_root = Path(__file__).parent.parent.parent
+ template_path = project_root / "api-template.md"
+ output_path = project_root / "api.md"
+
+ # Download and parse OpenAPI spec
+ try:
+ spec = download_openapi_spec()
+ except Exception as e:
+ print(f"Error downloading OpenAPI spec: {e}", file=sys.stderr)
+ sys.exit(1)
+
+ # Read template
+ if not template_path.exists():
+ print(f"Template file not found: {template_path}", file=sys.stderr)
+ sys.exit(1)
+
+ with open(template_path, "r") as f:
+ template_content = f.read()
+
+ # Generate API operations documentation
+ api_docs = generate_api_docs(spec)
+
+ # Replace placeholder in template
+ if "" not in template_content:
+ print("Warning: placeholder not found in template", file=sys.stderr)
+ final_content = template_content + "\n\n" + api_docs
+ else:
+ final_content = template_content.replace("", api_docs)
+
+ # Write output
+ with open(output_path, "w") as f:
+ f.write(final_content)
+
+ print(f"Successfully updated {output_path}")
+
+
+if __name__ == "__main__":
+ update_api_md()