All jobs

Generate a Typed API Client for a FastAPI Service

Yes. Your backend sends one request per customer; an agent runs their FastAPI service, generates a TypeScript client from its OpenAPI and returns the files.

Run this on RushIn a Project that has your repository.

What the agent does

The agent's first job is to get the service running, because the client should come from the OpenAPI document the live server serves, not from a guess at the routes. In a FastAPI project like fastapi/full-stack-fastapi-template it reads backend/pyproject.toml (requires-python = ">=3.14,<4.0", fastapi[standard]>=0.141.1, SQLModel, Alembic, psycopg[binary]) and .python-version (3.14), installs with uv from uv.lock, and starts Postgres, which compose.yml runs as postgres:18. It takes the settings from the repository's .env (POSTGRES_PASSWORD, FIRST_SUPERUSER=admin@example.com, FIRST_SUPERUSER_PASSWORD), runs backend/scripts/prestart.sh, which applies alembic upgrade head and creates the first superuser through app/initial_data.py, and starts the API with fastapi run.

With the server up, it fetches the document from the path app/main.py sets, openapi_url=f"{settings.API_V1_STR}/openapi.json", which is /api/v1/openapi.json. The same file sets generate_unique_id_function=custom_generate_unique_id, which names each operation {tag}-{route name} (items-read_items, users-read_user_me), so operation ids are stable and the generated methods get readable names. The repository already generates its own front-end client this way: scripts/generate-client.sh dumps app.main.app.openapi() and runs @hey-api/openapi-ts 0.97.3 with frontend/openapi-ts.config.ts, which groups operations into {{name}}Service classes by tag and strips the tag prefix from method names. The agent reuses that tool and naming so the customer's client matches the code they already know, but writes the output to a separate directory and uses the fetch client so the package has no Axios dependency.

Then it proves the client works. It compiles the generated types.gen.ts, sdk.gen.ts and client.gen.ts with tsc --noEmit in strict mode, and writes smoke.ts, which signs in through POST /api/v1/login/access-token with the superuser from .env, then calls three read endpoints through the generated SDK: GET /api/v1/utils/health-check/, GET /api/v1/users/me and GET /api/v1/items/. It runs the script against the server in the sandbox and saves what it printed. If a call fails because the document and the server disagree, such as a response that does not match its declared model, the agent records it rather than patching the generated code by hand.

Nothing is pushed to the customer's repository. The client, the exported openapi.json, smoke.ts and its output are the files that come back.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Start this FastAPI service in the sandbox the way the repository does: install with uv, start Postgres, take settings from .env, run backend/scripts/prestart.sh, then start the API. Fetch the OpenAPI document from the running server at the openapi_url set in app/main.py and save it as openapi.json. Generate a typed TypeScript client for every endpoint with @hey-api/openapi-ts and the fetch client, in a directory outside the repository, keeping the operation names the service defines. Compile it with tsc --noEmit in strict mode. Write smoke.ts that signs in with the first superuser from .env and calls three read endpoints through the generated client against the running server, and save its output as smoke-output.txt. Return the client as client.zip, plus openapi.json, smoke.ts and smoke-output.txt, as files. Do not change or push the repository.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

The files arrive as artifacts on the session: client.zip with the generated TypeScript (types.gen.ts for every request and response model, sdk.gen.ts with one method per operation, client.gen.ts with the configured fetch client), the openapi.json it was generated from, smoke.ts, and smoke-output.txt with the three responses as the script printed them. Each artifact carries name, mime_type and a url on api.prix.dev that downloads it with no key for an hour; reading the session again returns fresh links. The run.completed webhook's data.result carries the agent's summary: which endpoints the client covers and whether the compile and the smoke run passed.

Your backend downloads the artifacts and delivers them to the customer, as a package in their dashboard or a release in their registry. Before publishing a client, check that tsc passed, that smoke-output.txt shows the three real responses, and that the operation names in sdk.gen.ts match the customer's routes. Keeping openapi.json with the client lets the customer regenerate it themselves and see exactly which version of their API it reflects.

Run it on every customer API release

A client is stale the day the API changes. When a customer tags a release, your GitHub webhook handler starts a session with the repository at that tag (https://github.com/<owner>/<repo>/tree/<tag> in resources) and an Idempotency-Key made from the repository and tag, so GitHub redelivering the event does not start a second generation.

When run.completed arrives, your handler reads the session, downloads client.zip and smoke-output.txt from the artifact URLs within the hour they are valid, and publishes the client under the same version as the customer's release. A run.failed event means the service did not start or the client did not compile, and the transcript shows which step.

The use case this job follows, with its automation and trigger: Run an agent per customer task.

Why run it on Rush?

Generating a client from the real OpenAPI document means running the customer's service: their Python version, their dependencies, a Postgres database, their migrations. Doing that per customer on your own servers is a provisioning problem. In a Rush session the agent brings the service up in a sandbox of its own, with a database that exists only for this run, and the customer's code never runs on your machines.

The smoke test is what makes the client deliverable. A generator can emit code that compiles against a document the server does not honor; calling three endpoints through the generated SDK against the running server shows that it does. The output file and the transcript are the proof you hand the customer with the package, and many customers' clients can be generated at once.

Frequently asked questions

Why run the server instead of reading openapi.json from the repository?

FastAPI builds the document from the code at runtime, and many projects do not commit it. Fetching it from the running server gives the document the service actually serves, including the openapi_url and operation ids the app configures, and the same server is then there for the smoke test.

Can I choose the generator or the HTTP client?

Yes, in the prompt. This one asks for @hey-api/openapi-ts with the fetch client because the template already uses that generator; you can ask for openapi-typescript with openapi-fetch, an Axios client, or a specific output layout, and the agent follows it.

What if the service needs secrets to start?

Save them as Project secrets with POST /o/{org}/secrets and start the session in that Project with POST /o/{org}/p/{project}/sessions. The agent reads each one as an environment variable. For a template like this one the committed .env is enough for the sandbox.

How long are the download links valid?

An hour, shown in each artifact's url_expires_at. Reading the session again with GET /o/{org}/sessions/{id} returns fresh links, and GET /o/{org}/sessions/{id}/artifacts/{name} with the key downloads a file at any time.