Integrate with Claude Code
A copy-paste prompt that drives Claude Code to scaffold a typed Suzanne client in your repo's style.
The block below is a complete, self-contained prompt. Copy it into Claude Code (or any LLM coding agent) inside your project. The agent will read your existing stack, pick a sensible place for the client, and scaffold typed wrappers + tests for the Suzanne API.
You'll need:
- A Suzanne API key (
sk_test_...orsk_live_...) - Claude Code running inside the repo you want to integrate the API into
How to use
- Open Claude Code in your project root.
- Paste the prompt below as your first message.
- Replace
SUZANNE_API_KEYin your env / secret store before running anything generated. - The agent will ask 1–2 clarifying questions, then write the integration.
The prompt: copy everything between the lines below
You're integrating the Suzanne API (text/photo → 3D mesh generation) into this project. The customer-facing API is documented inline below. DO NOT fetch external docs. Read the project's current language, framework, package manager, and HTTP-client conventions first, then build the integration in that style.
## Goal
Add a small, typed, well-tested client for the Suzanne API. The client should:
- Submit generation jobs (text-to-3d and photo-to-3d, single OR multi-photo)
- Poll until completion with a sensible timeout
- Download the resulting GLB (or OBJ/STL) bytes
- Handle errors cleanly and surface them as native exceptions in this codebase's style
- Be testable: mock the HTTP layer with whatever this project already uses
## API contract (everything you need is here)
**Base URL:** `https://api.suzanne3d.com`
**Auth:** `Authorization: Bearer <SUZANNE_API_KEY>` (read from env, NEVER hardcode)
### Endpoints
1. **`POST /v1/generations/text-to-3d`**
Body:
```json
{
"model": "sculptor",
"prompt": "string (1-4000 chars)",
"params": { "faces": 100000, "pbr": true },
"outputs": ["glb"]
}
```
Returns `202` with `{ "job_id": "job_<uuid>", "status": "queued", "created_at": "...", "billable_amount_cents": 0 }`.
2. **`POST /v1/generations/photo-to-3d`**
Body, pick ONE of `images_inline` or `images_upload_ids`:
```json
{
"model": "sculptor",
"images_upload_ids": {
"front": "upl_...",
"back": "upl_...", // optional
"left": "upl_...", // optional
"right": "upl_..." // optional
},
"params": { "faces": 100000, "pbr": true },
"outputs": ["glb"]
}
```
- 1 photo → routes to single-image vendor automatically.
- 2-4 photos → routes to multi-view vendor automatically.
- `images_inline: { "front": "<base64>" }` is supported for single-photo only.
3. **`POST /v1/uploads`** (no body, just auth header)
Returns `{ "upload_id": "upl_<uuid>", "upload_url": "<presigned-s3-put-url>", "expires_at": "..." }`. Then the client `PUT`s the JPEG/PNG bytes to `upload_url`.
**CRITICAL:** Do NOT send a `Content-Type` header on the PUT. The presigned URL is signed without one and S3 will return 403 otherwise. In `curl` use `-H "Content-Type:"`; in `fetch` just omit it; in `requests.put` use `headers={"Content-Type": None}` or pass `data=` not `files=`.
4. **`GET /v1/jobs/{job_id}`** (poll status):
```json
{
"job_id": "job_...",
"status": "queued | running | done | failed | cancelled",
"outputs": [{ "format": "glb", "download_url": "..." }],
"error": null | { "code": "...", "message": "..." }
}
```
Typical latency: 30 s – 4 min. Poll every 5–10 s, give up after 20 min.
5. **`POST /v1/jobs/{job_id}/cancel`** cancels a queued/running job. Returns `{ "job_id", "status", "already_terminal": bool }`.
6. **`GET /v1/models/{job_id}/download?format={glb|obj|stl}`** returns a `302` redirect to a 15-min presigned S3 URL. Follow redirects automatically.
### Parameters
- `faces`: one of `40000`, `100000`, `500000`, `1500000` (default `100000`). For multi-photo, values >20k are silently clamped to 20k server-side.
- `pbr`: boolean (default `true`).
- `outputs`: any subset of `["glb", "obj", "stl"]` (default `["glb"]`). GLB is always produced; OBJ/STL are converted server-side.
### Errors
Error responses are JSON: `{ "error": { "type", "code", "message", "request_id" } }`.
Important codes to handle:
- `validation_error`, `invalid_param`: fix the request body
- `unauthorized`: API key missing or revoked
- `concurrent_limit_reached`: back off and retry
- `inline_multi_photo_not_supported`: use `images_upload_ids` for multi-view
- `vendor_*` (from job.error when status=failed): the upstream model failed; surface to the caller, don't auto-retry
### Idempotency
For both `/v1/generations/*` endpoints, you can send `Idempotency-Key: <uuid>` to make POSTs safe to retry. Same key + same body within 24 h returns the original job (no duplicate). Same key + different body → 409. Recommend the client always generates a fresh UUID per logical generation request.
## How to do this work
1. **Read the project first.** Identify:
- The language (Python / Node / Go / etc.) and version.
- The HTTP client convention already in use (`requests`, `httpx`, `axios`, `fetch`, `reqwest`, ...).
- The test framework and mocking style.
- Where shared clients/services already live (e.g. `src/clients/`, `internal/services/`, `lib/`).
- How secrets are loaded (env, config service, Doppler, etc.).
2. **Ask 1–3 clarifying questions ONLY if necessary.** For example:
- "I see both `requests` and `httpx` in your deps; which should I use?"
- "Should the client be sync, async, or both?"
- Don't ask about anything you can decide from reading the repo.
3. **Implement the client.** Conventions:
- A single `SuzanneClient` class (or module) with methods `create_text_job`, `create_photo_job`, `upload_image`, `get_job`, `cancel_job`, `download_output`, and a high-level `generate_and_wait(...)` that combines submit + poll + download.
- The poll method takes an optional `timeout` (default 20 min) and `poll_interval` (default 5 s).
- Use typed structs / dataclasses / interfaces for request and response bodies. No untyped `dict[str, Any]` in the public surface.
- Read the API key from environment (`SUZANNE_API_KEY`). Fail loudly if missing.
- When uploading via presigned URL, DO NOT set `Content-Type` (see CRITICAL note above).
- Surface errors as a `SuzanneAPIError(code, message, request_id)` exception class, with subclasses for the cases the caller will likely want to handle differently (e.g. `SuzanneRateLimitError`, `SuzanneJobFailedError`).
4. **Write tests.** Mock the HTTP layer at the boundary of whatever client you used. Cover:
- Happy path: text-to-3d submit + poll + download.
- Happy path: photo-to-3d with 2 uploaded photos.
- Error path: job ends with `status: "failed"`, and `SuzanneJobFailedError` is raised with the vendor error code.
- Error path: poll timeout.
- Verify the upload PUT does not include a `Content-Type` header.
5. **Document usage.** Add a short section to the README (or a new doc file if the project has a `docs/` folder) showing 5-10 lines of "here's how to generate a model from a prompt". Match whatever idiom the project's other docs use.
6. **Don't add dependencies you don't need.** Use what's already in `requirements.txt` / `package.json` / `go.mod` unless something is genuinely missing.
7. **Don't:**
- Don't hardcode the API key, even in tests. Use a fixture or env var.
- Don't wrap every method in retry loops. The API is reliable; only retry on `concurrent_limit_reached` and 5xx (with exponential backoff, max 3 attempts).
- Don't expose a streaming interface unless this codebase has a clear pattern for it. The bytes come from a 302-redirected S3 URL, simplest is to return them buffered.
- Don't try to be clever about output-format conversion in client code. The server does it; just request `outputs: ["glb", "stl"]` and download what you need.
Show me the plan before you start coding, then proceed.
What you'll get
The agent will produce, in the style of your existing codebase:
- A typed
SuzanneClient(sync, async, or both, whatever matches your repo) - Domain-specific exceptions
- A
generate_and_waitconvenience that handles the submit → poll → download flow - Tests mocking the HTTP layer
- A short README section showing usage
Tips for getting better output
- Run the prompt from your repo root, not an empty directory. The agent infers your stack from existing code.
- If your project uses a non-obvious HTTP client (e.g. a custom wrapper), point that out in a follow-up message: "Use our existing
internal/http/Clientnot raw axios." - For projects with strict typing (TypeScript, Pydantic, Go), tell the agent which types library to use if the convention isn't obvious from
package.json/pyproject.toml. - Production hardening (retry policies, observability, circuit breakers): ask for those in a second pass; keep the first pass focused on correctness.