---
name: orville-cad-api
description: Use this skill when building an integration with the Orville public CAD API, including creating CAD jobs, attaching reference images, polling results, talking to the review assistant, reading message history, iterating on a CAD job, downloading STEP files, and checking workspace API billing.
---

# Orville CAD API

Use the Orville API when a client wants to create CAD from text and optional reference images, review the resulting model, read the resulting conversation, iterate on the result, and download generated STEP files.

Base URL:

```bash
https://www.ballistalabs.ai
```

Auth:

```bash
Authorization: Bearer $ORVILLE_API_KEY
```

Scopes:

- `cad:write`: create CAD jobs, send follow-up iterations, and write review messages.
- `cad:read`: list jobs, read job status, read message history, review completed jobs, read artifacts, and download STEP files.
- `billing:read`: read workspace public API billing balance.

## Hard Rules

- Attach images directly to CAD create/follow-up requests with multipart form field `images`.
- CAD create, follow-up, and review requests require `Idempotency-Key`; reuse the same key only for exact retries.
- Image limits: max 5 images, max 10MB each, JPEG/PNG/WebP/GIF/HEIC/HEIF.
- Poll no sooner than `poll_after_seconds`. If absent, poll every 1-2 minutes until `status` is `completed` or `failed`.
- If a request returns `rate_limited`, wait for the `Retry-After` header before retrying.
- Keep `request_id` from responses in logs. Use it when reporting API issues.
- Read completed job cost from `cost.charged_cents` on the job response.
- Use `GET /api/v1/cad/jobs` to find recent workspace jobs before creating a duplicate job.

## Prompting Advice

Prompts should be defined only enough to articulate the constraints of the problem. Prompts that are only a few sentences long tend to do well. Even a couple words is sometimes sufficient. Requests that are many paragraphs long tend to produce poor results. Requests that specify the solution (i.e. exact dimension requirements of numerous parts/interfaces) produce poor results. It is better to say "generate a coffee mug" than "generate a coffee mug of XXmm diameter and YYmm height with a wall thickness of tmm" if those dimensions don't drive the utility of the result. Orville can and will think hard about the physical constraints of the problem. Orville understands manufacturing constraints. In general, not specifying a manufacturing process will mean it will choose the best processes given the problem. If manufacturing is a constraint on the problem, for instance, when a users wants to 3d print the result themselves, it is best to mention that the user has access to a 3d printer and would like to utilize it. Its important to not overconstrain manufacturing methods. For example, a user could 3D print an object, but use commercially available fasteners in the assembly. It would not be wise to instruct orville to design around 3d printed fasteners.

## Create A CAD Job

Use multipart form data when attaching images:

```bash
curl -X POST "$ORVILLE_BASE_URL/api/v1/cad/jobs" \
  -H "Authorization: Bearer $ORVILLE_API_KEY" \
  -H "Idempotency-Key: 87b5972d-05b9-4f0a-9b68-97c53fd4f840" \
  -F 'prompt=Create a mounting plate with rounded corners and two through holes.' \
  -F 'images=@mounting-plate-sketch.png'
```

Use JSON only when there are no images:

```bash
curl -X POST "$ORVILLE_BASE_URL/api/v1/cad/jobs" \
  -H "Authorization: Bearer $ORVILLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9e16de21-3919-48f8-9df3-73a2b993fa1f" \
  -d '{
    "prompt": "Create a simple flat rectangular bracket with two through holes."
  }'
```

Response:

```json
{
  "id": "cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f",
  "status": "queued",
  "poll_after_seconds": 30,
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

## List Jobs

List recent non-hidden CAD jobs in the workspace. This includes jobs created in the webapp and jobs created through the public API.

```bash
curl "$ORVILLE_BASE_URL/api/v1/cad/jobs?limit=20&q=mounting%20plate" \
  -H "Authorization: Bearer $ORVILLE_API_KEY"
```

Response:

```json
{
  "jobs": [
    {
      "id": "cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f",
      "status": "completed",
      "source": "webapp",
      "title": "Mounting plate",
      "prompt_preview": "Create a mounting plate with rounded corners.",
      "last_message": "Increase the thickness to 8 mm.",
      "latest_revision_id": "rev_4FIoIk2zfzNGakNvYMXDAA",
      "created_at": "2026-05-20T14:00:00.000Z",
      "updated_at": "2026-05-20T14:04:12.000Z"
    }
  ],
  "next_cursor": null,
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

Use `limit` for page size. Default is 20 and max is 100. Use `next_cursor` as the next request's `cursor`. Use `q` for lightweight search across project titles and original prompts. Continue a listed job with `GET /api/v1/cad/jobs/{job_id}`, `GET /api/v1/cad/jobs/{job_id}/messages`, `POST /api/v1/cad/jobs/{job_id}/messages`, and `POST /api/v1/cad/jobs/{job_id}/review`.

## Poll A Job

```bash
curl "$ORVILLE_BASE_URL/api/v1/cad/jobs/cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f" \
  -H "Authorization: Bearer $ORVILLE_API_KEY"
```

Statuses: `queued`, `running`, `completed`, `failed`.

Before completion, `explanation`, `latest_revision_id`, `assembly_tree`, and `artifacts` can be null or empty.

Completed response shape:

```json
{
  "id": "cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f",
  "status": "completed",
  "created_at": "2026-05-20T14:00:00.000Z",
  "updated_at": "2026-05-20T14:04:12.000Z",
  "poll_after_seconds": null,
  "explanation": "The mounting plate features a flat rectangular profile...",
  "cost": {
    "currency": "usd",
    "charged_cents": 200
  },
  "latest_revision_id": "rev_4FIoIk2zfzNGakNvYMXDAA",
  "assembly_tree": {
    "label": "mounting_plate",
    "kind": "step",
    "filename": "449_mounting_plate.step",
    "artifact_id": "art_pW89wqwr67T7O00Ri_8ThA",
    "children": []
  },
  "artifacts": [
    {
      "id": "art_pW89wqwr67T7O00Ri_8ThA",
      "label": "mounting_plate",
      "filename": "449_mounting_plate.step",
      "kind": "step",
      "download_url": "/api/v1/artifacts/art_pW89wqwr67T7O00Ri_8ThA/download"
    }
  ],
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

## Iterate

Send a follow-up only after the job is not actively running. Follow-ups support the same input shape as create: required `prompt`, optional multipart `images`.

```bash
curl -X POST "$ORVILLE_BASE_URL/api/v1/cad/jobs/cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f/messages" \
  -H "Authorization: Bearer $ORVILLE_API_KEY" \
  -H "Idempotency-Key: cfb418cc-f8dc-471d-a418-84f612f71449" \
  -F 'prompt=Increase the thickness to 8 mm and move the holes farther apart.' \
  -F 'images=@markup.png'
```

Response:

```json
{
  "id": "cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f",
  "status": "queued",
  "message_id": "3b6b1e3a-5a40-475b-bef0-6d52e2fdd16c",
  "poll_after_seconds": 30,
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

## Review A Completed Job

Ask the review assistant about a completed CAD job without starting a CAD iteration. Review requests require both `cad:read` and `cad:write`, use JSON, and require `Idempotency-Key`.

```bash
curl -X POST "$ORVILLE_BASE_URL/api/v1/cad/jobs/cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f/review" \
  -H "Authorization: Bearer $ORVILLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: f0fb3637-75e8-44f9-b2c9-5e3dce39c631" \
  -d '{
    "prompt": "Review the current model for manufacturability and assembly risks.",
    "revision_id": "rev_4FIoIk2zfzNGakNvYMXDAA",
    "viewer_state": {
      "camera": {
        "position": [120, 80, 90],
        "target": [0, 0, 0],
        "up": [0, 0, 1]
      }
    }
  }'
```

Response:

```json
{
  "job_id": "cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f",
  "revision_id": "rev_4FIoIk2zfzNGakNvYMXDAA",
  "message_id": "2df6c964-bf0b-4e9a-998b-1c2ad907fcb6",
  "assistant_message_id": "77f52fe8-d731-48b8-85fb-1748ef7409ea",
  "review": {
    "mode": "review",
    "message": "The bracket is generally straightforward to manufacture...",
    "model": "review-agent",
    "iteration_proposal": null,
    "viewer_actions": []
  },
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

Optional `viewer_state` can include host CAD viewer context such as camera, selection, section, annotations, or snapshots. Snapshot entries can include `label`, `mime_type`, `data_base64`, and `source: "viewer_snapshot"`.

## Message History

Fetch the public conversation for a CAD job:

```bash
curl "$ORVILLE_BASE_URL/api/v1/cad/jobs/cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f/messages" \
  -H "Authorization: Bearer $ORVILLE_API_KEY"
```

Response:

```json
{
  "job_id": "cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f",
  "messages": [
    {
      "id": "3b6b1e3a-5a40-475b-bef0-6d52e2fdd16c",
      "role": "user",
      "content": "Create a mounting plate from this sketch.",
      "created_at": "2026-05-20T14:00:00.000Z",
      "images": [
        {
          "id": "file_6FZkCpLcH7s3VRYB0a8quA",
          "filename": "mounting-plate-sketch.png",
          "content_type": "image/png",
          "size_bytes": 184221
        }
      ],
      "revision_id": null,
      "revision": null,
      "artifacts": [],
      "review": null
    },
    {
      "id": "8c7cfd22-c8ed-4247-9907-4d469c0c7ad3",
      "role": "assistant",
      "content": "The mounting plate features a flat rectangular profile...",
      "created_at": "2026-05-20T14:04:12.000Z",
      "images": [],
      "revision_id": "rev_4FIoIk2zfzNGakNvYMXDAA",
      "revision": "A",
      "artifacts": [
        {
          "id": "art_pW89wqwr67T7O00Ri_8ThA",
          "label": "mounting_plate",
          "filename": "449_mounting_plate.step",
          "kind": "step",
          "download_url": "/api/v1/artifacts/art_pW89wqwr67T7O00Ri_8ThA/download"
        }
      ],
      "review": null
    },
    {
      "id": "77f52fe8-d731-48b8-85fb-1748ef7409ea",
      "role": "assistant",
      "content": "The bracket is generally straightforward to manufacture...",
      "created_at": "2026-05-20T14:07:12.000Z",
      "images": [],
      "revision_id": null,
      "revision": null,
      "artifacts": [],
      "review": {
        "mode": "review",
        "message": "The bracket is generally straightforward to manufacture...",
        "model": "review-agent",
        "iteration_proposal": null,
        "viewer_actions": []
      }
    }
  ],
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

Message history returns user and assistant messages only. System messages, hidden text, raw storage paths, signed image URLs, and internal artifact payloads are not exposed. Review assistant messages include a `review` object with mode, message, optional viewer actions, and optional iteration proposal.

## List Artifacts

```bash
curl "$ORVILLE_BASE_URL/api/v1/cad/jobs/cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f/artifacts" \
  -H "Authorization: Bearer $ORVILLE_API_KEY"
```

Response:

```json
{
  "job_id": "cadjob_385f3130-1ebc-40b7-85d9-6ded5ecc8a0f",
  "revision_id": "rev_4FIoIk2zfzNGakNvYMXDAA",
  "assembly_tree": {
    "label": "mounting_plate",
    "kind": "step",
    "filename": "449_mounting_plate.step",
    "artifact_id": "art_pW89wqwr67T7O00Ri_8ThA",
    "children": []
  },
  "artifacts": [
    {
      "id": "art_pW89wqwr67T7O00Ri_8ThA",
      "label": "mounting_plate",
      "filename": "449_mounting_plate.step",
      "kind": "step",
      "download_url": "/api/v1/artifacts/art_pW89wqwr67T7O00Ri_8ThA/download"
    }
  ],
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

## Download STEP

Default behavior is a `302` redirect to a short-lived signed STEP URL:

```bash
curl -L "$ORVILLE_BASE_URL/api/v1/artifacts/art_pW89wqwr67T7O00Ri_8ThA/download" \
  -H "Authorization: Bearer $ORVILLE_API_KEY" \
  -o mounting_plate.step
```

Use `redirect=false` to receive a JSON signed URL instead:

```bash
curl "$ORVILLE_BASE_URL/api/v1/artifacts/art_pW89wqwr67T7O00Ri_8ThA/download?redirect=false" \
  -H "Authorization: Bearer $ORVILLE_API_KEY"
```

```json
{
  "artifact_id": "art_pW89wqwr67T7O00Ri_8ThA",
  "filename": "449_mounting_plate.step",
  "url": "https://storage.example.com/signed-step-url",
  "expires_in_seconds": 900,
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

## Billing

Use the billing endpoint to check the current workspace balance. Use the completed CAD job response to check the actual completed job cost.

```bash
curl "$ORVILLE_BASE_URL/api/v1/billing/balance" \
  -H "Authorization: Bearer $ORVILLE_API_KEY"
```

Response:

```json
{
  "currency": "usd",
  "available_balance_cents": 2500,
  "cad_job_hold_cents": 200,
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

## Errors

Error shape:

```json
{
  "error": {
    "code": "job_already_running",
    "message": "CAD job already has an active run"
  },
  "request_id": "req_8f3c2d0e98ab4e53981f481d661d44ab"
}
```

Common status codes:

- `400`: invalid input, invalid body, image issue, or missing `Idempotency-Key`.
- `401`: missing, invalid, revoked, or expired API key.
- `402`: workspace billing cannot start another CAD job.
- `403`: API key lacks the required scope.
- `404`: job or artifact not found.
- `409`: idempotency conflict or active job run.
- `429`: rate limit exceeded, or workspace balance is too low for the active CAD job load.
- `503`: billing/background service check is temporarily unavailable.

Common error codes:

- `missing_prompt`
- `prompt_too_long`
- `invalid_json`
- `invalid_form_data`
- `json_images_unsupported`
- `unsupported_image_field`
- `invalid_file_type`
- `file_too_large`
- `too_many_images`
- `idempotency_key_required`
- `idempotency_key_conflict`
- `job_already_running`
- `job_not_completed`
- `cad_revision_required`
- `review_agent_failed`
- `review_agent_unavailable`
- `billing_required`
- `billing_needs_review`
- `insufficient_balance`
- `projected_balance_exceeded`
- `rate_limited`

## Rate Limits

Current API keys use the standard tier:

- CAD creates and follow-ups: 10 requests per minute per workspace.
- Review assistant messages: 20 requests per minute per workspace.
- Job status polling: 3 requests per 10 seconds per API key per job, plus 600 read requests per minute per workspace.
- Artifact list/download: 120 requests per minute per API key.
- Billing balance: 60 requests per minute per API key.

On `rate_limited` HTTP 429 responses, wait for `Retry-After` before retrying.

## Machine Reference

Use the OpenAPI JSON only for schema/reference checks:

```text
https://www.ballistalabs.ai/api/v1/openapi.json
```

## Versioning

Documented `/api/v1` endpoints, request fields, response fields, status meanings, and error codes are stable. Breaking changes ship under a new version such as `/api/v2`. New nullable fields and new endpoints can be added in v1.
