# Generate a Typed API Client for a Spring Boot Service

Yes. An agent boots the customer's Spring Boot service, exports its OpenAPI with springdoc, generates a TypeScript client and runs it against the server.

[Run this on Rush](https://cloud.getrush.ai/run/generate-api-clients/spring-boot/), in a Project that has your repository.

## What the agent does

The agent first works out how the service builds and runs. In a Spring Boot app like spring-projects/spring-petclinic, `pom.xml` inherits `spring-boot-starter-parent` `4.1.0`, sets `<java.version>17</java.version>` (and the `maven-enforcer-plugin` refuses an older JVM), and pulls in `spring-boot-starter-webmvc`, `spring-boot-starter-data-jpa`, `spring-boot-starter-thymeleaf`, `spring-boot-starter-actuator`, H2, MySQL and Postgres drivers. `application.properties` sets `database=h2`, loading `db/h2/schema.sql` and `db/h2/data.sql` at startup, so the default profile needs no external database, and `management.endpoints.web.exposure.include=*` exposes every actuator endpoint. The agent installs a Java 17 or newer JDK and starts the app with `./mvnw spring-boot:run`, the Maven wrapper the repository ships.

Then it looks for the OpenAPI document and, in this repository, finds there is none: no `springdoc-openapi` dependency in `pom.xml` or `build.gradle`. The prompt allows for that. The agent adds a `springdoc-openapi-starter-webmvc-ui` release that supports the project's Spring Boot version to the sandbox copy of `pom.xml` only, sets `springdoc.show-actuator=true` on the command line so actuator endpoints are included, restarts, and fetches `/v3/api-docs`. It never commits or pushes that change; the report says it was made and why, so the customer knows the document came from their running code plus one added library.

The document shows what kind of API this is, and the agent says so. PetClinic is a server-rendered Thymeleaf app: `OwnerController`, `PetController` and `VisitController` return view names for HTML pages and form posts, and only `VetController.showResourcesVetList`, mapped to `GET /vets` with `@ResponseBody`, returns JSON, a `Vets` object whose list serializes as `vetList`. The agent generates a TypeScript client for every operation with `openapi-typescript` and `openapi-fetch`, compiles it with `tsc --noEmit`, and writes `endpoints.md` sorting the operations into JSON endpoints a client can use as data and HTML endpoints that return a page as a string.

For the smoke test it writes `smoke.ts`, which calls three read endpoints through the generated client against the running server: `GET /vets`, `GET /actuator/health` and `GET /owners/{ownerId}` for an owner the H2 seed data creates. The first two return JSON the client types; the third returns HTML, and the script prints its status and content type to show how the client handles it. The output goes to `smoke-output.txt`.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Start this Spring Boot service in the sandbox with its Maven wrapper and the JDK its pom.xml requires. If it serves no OpenAPI document, add springdoc-openapi-starter-webmvc-ui in a version that supports this Spring Boot release to the sandbox copy of pom.xml only, start it with springdoc.show-actuator=true, and fetch /v3/api-docs; never commit or push that change. Save the document as openapi.json. Generate a typed TypeScript client for every endpoint with openapi-typescript and openapi-fetch, and compile it with tsc --noEmit. Write endpoints.md listing each operation as JSON or HTML by its response content type. Write smoke.ts that calls three read endpoints through the client against the running server, printing status, content type and body, and save its output as smoke-output.txt. Return client.zip, openapi.json, endpoints.md, smoke.ts and smoke-output.txt as files.",
  "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 session's artifacts are the five files, each downloadable from its `url` on `api.prix.dev` without a key for an hour: `client.zip` with the generated `schema.d.ts` types and a small typed `openapi-fetch` wrapper, `openapi.json` as `/v3/api-docs` served it, `endpoints.md`, `smoke.ts` and `smoke-output.txt`. The agent's final answer, the session's `result` and the webhook's `data.result`, states whether springdoc had to be added, whether the client compiled and what each smoke call returned.

`endpoints.md` decides what you deliver. For an API-first service most operations are JSON and the client is ready to publish. For a server-rendered app like this one, a customer who wants a client for their data learns that only `GET /vets` and the actuator endpoints return JSON, which is a finding worth showing them before anyone builds on it. Check that `smoke-output.txt` shows real JSON from `/vets` and `/actuator/health` before shipping the package.

## Run it on every tagged release of the customer's service

When a customer tags a release, start a session with `https://github.com/<owner>/<repo>/tree/<tag>` in `resources` and an `Idempotency-Key` made from the repository and tag. Each tag gets its own sandbox and its own build, so the client you publish matches the code at that tag rather than whatever was on the default branch when the job ran.

The `run.completed` webhook tells your backend when the files are ready. Download `client.zip` before its link expires (reading the session again returns a fresh one), publish it under the customer's release version, and keep `openapi.json` beside it so the next generation can be compared against this one.

## Why run it on Rush?

Building a Spring Boot service takes a JDK, a Maven download of the whole dependency tree and a running JVM, and generating a real document may mean adding springdoc to a build the customer owns. In a Rush sandbox the agent does that on a disposable copy: the added dependency, the build output and the running server exist only there, and nothing reaches the customer's repository unless you ask for it in a separate request.

The client is checked against the running service, not just generated. The compile, the three calls and their responses are kept in the session with every command the agent ran, so the package you hand over comes with the evidence that it talks to the server. Generations for many customers' services can run at the same time, each in its own sandbox.

## Frequently asked questions

### My service has no OpenAPI document. Will this still work?

Yes. The prompt tells the agent to add springdoc-openapi to the sandbox copy of `pom.xml` when the service serves no document, then fetch `/v3/api-docs` from the running app. The change is never committed; the result says it was made so the customer can add it themselves.

### What happens with endpoints that return HTML pages?

They are in the document and the client, typed as returning a string, and `endpoints.md` marks them as HTML. A Thymeleaf controller that returns a view name is a page, not data, and the report makes that clear rather than presenting it as an API.

### Does it need a real database?

Only if the default profile does. PetClinic's `application.properties` uses H2 with seed data, so nothing else is needed. For a service whose default profile needs Postgres or MySQL, the agent starts that database in the sandbox from the settings the app reads.

### Does it work with Gradle builds?

Yes. The agent uses the build the repository defines, `./gradlew bootRun` for Gradle and `./mvnw spring-boot:run` for Maven, and adds springdoc to `build.gradle` or `pom.xml` accordingly, again only in the sandbox.
