Suzanne
Browse docs

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_... or sk_live_...)
  • Claude Code running inside the repo you want to integrate the API into

How to use

  1. Open Claude Code in your project root.
  2. Paste the prompt below as your first message.
  3. Replace SUZANNE_API_KEY in your env / secret store before running anything generated.
  4. 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_wait convenience 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/Client not 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.