# Projects API

List, create, update, delete, and search engineering projects with nested jobs and operations.

Source: https://support.toolingmetrics.com/en/api/reference/projects/
Updated: 2026-10-29

Base path: `/v1/organizations/:organization_id/projects`

## Endpoints

| Method | Path | Description |
|---|---|---|
| `GET` | `/projects` | List all projects (summary) |
| `GET` | `/projects/:id` | Get a project with jobs and operations |
| `POST` | `/projects` | Create a project |
| `PATCH` | `/projects/:id` | Update a project |
| `DELETE` | `/projects/:id` | Delete a project |
| `GET` | `/projects/search` | Search projects by name |

## Project object (detail)

```json
{
  "id": 8,
  "name": "Cylinder Block Rev2",
  "description": "...",
  "status": "released",
  "multiple_setup": false,
  "created_at": "2025-04-01T08:00:00Z",
  "updated_at": "2025-06-01T10:00:00Z",
  "created_by": { "id": 7, "email": "user@example.com" },
  "jobs": [
    {
      "id": 12,
      "name": "Op 10 — Facing",
      "order": 1,
      "status": "draft",
      "locked": false,
      "machine": { "id": 1, "designation": "DMG-001", "name": "DMG 5-axis" },
      "part": { "id": 5, "number": "P-0042", "description": "Cylinder Block" },
      "operations": [
        { "id": 20, "name": "Face milling" }
      ]
    }
  ]
}
```

## List response

The `GET /projects` index returns a compact summary — not the full nested structure:

```json
[
  { "id": 8, "name": "Cylinder Block Rev2", "status": "released", "locked": false },
  { "id": 9, "name": "Shaft Housing", "status": "draft", "locked": false }
]
```

## Parameters

### Create / update

| Parameter | Type | Description |
|---|---|---|
| `name` | string | Project name |
| `description` | string | Project description |
| `status` | string | One of `draft`, `released`, `approved`, `active`, `archived`. Approving a released project copies the products of its tool assemblies into items, created as the project's creator, so it also needs the key's user to be allowed to create such items and to update the existing ones; otherwise `401` |
| `multiple_setup` | boolean | Whether the project uses multiple machine setups |
| `picture` | string | Project image as a Base64-encoded PNG |
| `jobs_attributes` | array | Nested jobs (see below) |

Unlike [Parts](https://support.toolingmetrics.com/en/api/reference/parts/) and [Machines](https://support.toolingmetrics.com/en/api/reference/machines/), Projects auto-assign their organization from the `:organization_id` path segment — you do not need to (and cannot, `organization_id` isn't an accepted param) pass it again in the body. The same applies to each job in `jobs_attributes`.

### Jobs attributes

| Parameter | Type | Description |
|---|---|---|
| `id` | integer | Required when updating an existing job |
| `name` | string | Job name |
| `order` | integer | Display order |
| `machine_id` | integer | Assigned machine |
| `part_id` | integer | Workpiece part |
| `status` | string | Job status |
| `_destroy` | boolean | Pass `true` to delete the job |
| `operations_attributes` | array | Nested operations (see below) |

### Operations attributes

| Parameter | Type | Description |
|---|---|---|
| `id` | integer | Required when updating an existing operation |
| `name` | string | Operation name |
| `order` | integer | Display order within the job |
| `cycle_time` | integer | Estimated cycle time. **Must be a whole number** — `numericality: only_integer: true` rejects decimals with a `422` |
| `tool_number` | integer | T-number in the NC program |
| `tool_assembly_id` | integer | Assigned tool assembly model |
| `_destroy` | boolean | Pass `true` to delete the operation |

### List / search filters

| Parameter | Type | Description |
|---|---|---|
| `status` | string | Filter by status (list endpoint). **Also implicitly filters to `locked: false`** — passing `status` excludes locked projects even though you didn't ask for that filter. Omit `status` if you need locked projects included. |
| `q` | string | Case-insensitive substring match on project name (search endpoint) |

## Examples

```bash
# Search projects by name
curl "https://api.toolingmetrics.com/v1/organizations/12/projects/search?q=Cylinder" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Get a project with full job and operation detail
curl "https://api.toolingmetrics.com/v1/organizations/12/projects/8" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

```python
import requests

BASE = "https://api.toolingmetrics.com/v1"
HEADERS = {"Authorization": "Bearer YOUR_TOKEN"}

# Create a project with one job and one operation (CAM post-processor integration)
payload = {
    "name": "Cylinder Block Rev2",
    "status": "draft",
    "jobs_attributes": [
        {
            "name": "Op 10 — Facing",
            "order": 1,
            "machine_id": 1,
            "part_id": 5,
            "operations_attributes": [
                {
                    "name": "Face milling",
                    "order": 1,
                    "cycle_time": 750,
                    "tool_number": 1,
                    "tool_assembly_id": 42,
                }
            ],
        }
    ],
}

response = requests.post(
    f"{BASE}/organizations/12/projects",
    headers={**HEADERS, "Content-Type": "application/json"},
    json=payload,
)
response.raise_for_status()
project = response.json()
print(f"Created project #{project['id']}: {project['name']}")
```

## Common errors

| Status | Cause |
|---|---|
| `401` | The key's user may not make the change: for example approving a released project without the right to change the items its approval creates or updates |
| `404` | Project, machine, or part ID not found |
| `422` | Required fields missing or invalid status value |
