# Factory Control

Source: https://opvs.ai/docs/api/factory-control

## `GET` /api/v1/factory/control/worker-config

**Get Fleet Worker Config**

Every machine's switches in ONE call — the fleet switchboard's read.

**Parameters**

- `env_ids` (query, any, optional) — Comma-separated env ids (canonical UUID or `env_…` short code). Omit for every machine in the brand.
- `timeout` (query, any, optional) — Per-box budget in seconds (clamped 1–30, default 6)

```bash
curl -X GET 'https://app.opvs.ai/api/v1/factory/control/worker-config' \
  -H 'Authorization: Bearer <token>'
```

```python
import httpx

resp = httpx.get(
    "https://app.opvs.ai/api/v1/factory/control/worker-config",
    headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())
```

```javascript
const resp = await fetch("https://app.opvs.ai/api/v1/factory/control/worker-config", {
  method: "GET",
  headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);
```

```go
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://app.opvs.ai/api/v1/factory/control/worker-config", nil)
	req.Header.Set("Authorization", "Bearer <token>")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
```

**Responses**

- `200` — Successful Response
- `422` — Validation Error

## `GET` /api/v1/factory/control/environments/{env_id}/worker-config

**Get Worker Config**

Read an env's 5 operator switches (the dashboard panel's initial state).

**Parameters**

- `env_id` (path, string, required)

```bash
curl -X GET 'https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config' \
  -H 'Authorization: Bearer <token>'
```

```python
import httpx

resp = httpx.get(
    "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config",
    headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())
```

```javascript
const resp = await fetch("https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config", {
  method: "GET",
  headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);
```

```go
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config", nil)
	req.Header.Set("Authorization", "Bearer <token>")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
```

**Responses**

- `200` — Successful Response
- `422` — Validation Error

## `PATCH` /api/v1/factory/control/environments/{env_id}/worker-config

**Set Worker Config**

Change an env's worker config and return its independently read state.

**Parameters**

- `env_id` (path, string, required)

**Request body**

```bash
curl -X PATCH 'https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "enabled": true,
  "spawn_enabled": true,
  "git_pr_enabled": true,
  "route_enabled": true,
  "gate_enabled": true,
  "mode": "auto",
  "max_concurrent_builds": 0,
  "run_report_enabled": true,
  "poll_interval_seconds": 0.0
}'
```

```python
import httpx

resp = httpx.patch(
    "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config",
    headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
    json={"enabled": true, "spawn_enabled": true, "git_pr_enabled": true, "route_enabled": true, "gate_enabled": true, "mode": "auto", "max_concurrent_builds": 0, "run_report_enabled": true, "poll_interval_seconds": 0.0},
)
resp.raise_for_status()
print(resp.json())
```

```javascript
const resp = await fetch("https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
  body: JSON.stringify({"enabled": true, "spawn_enabled": true, "git_pr_enabled": true, "route_enabled": true, "gate_enabled": true, "mode": "auto", "max_concurrent_builds": 0, "run_report_enabled": true, "poll_interval_seconds": 0.0})
});
const data = await resp.json();
console.log(data);
```

```go
package main

import (
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"enabled": true, "spawn_enabled": true, "git_pr_enabled": true, "route_enabled": true, "gate_enabled": true, "mode": "auto", "max_concurrent_builds": 0, "run_report_enabled": true, "poll_interval_seconds": 0.0}`)
	req, _ := http.NewRequest("PATCH", "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config", body)
	req.Header.Set("Authorization", "Bearer <token>")
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
```

**Responses**

- `200` — Successful Response
- `422` — Validation Error

## `GET` /api/v1/factory/control/environments/{env_id}/pending

**Get Pending Cards**

List an env's cards paused for human approval + its board-default mode.

**Parameters**

- `env_id` (path, string, required)

```bash
curl -X GET 'https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/pending' \
  -H 'Authorization: Bearer <token>'
```

```python
import httpx

resp = httpx.get(
    "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/pending",
    headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())
```

```javascript
const resp = await fetch("https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/pending", {
  method: "GET",
  headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);
```

```go
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/pending", nil)
	req.Header.Set("Authorization", "Bearer <token>")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
```

**Responses**

- `200` — Successful Response
- `422` — Validation Error

## `POST` /api/v1/factory/control/environments/{env_id}/approve

**Approve Card**

Release a paused card so its build proceeds (brand admin only).

**Parameters**

- `env_id` (path, string, required)

**Request body**

```bash
curl -X POST 'https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/approve' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "task_id": "string"
}'
```

```python
import httpx

resp = httpx.post(
    "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/approve",
    headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
    json={"task_id": "string"},
)
resp.raise_for_status()
print(resp.json())
```

```javascript
const resp = await fetch("https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/approve", {
  method: "POST",
  headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
  body: JSON.stringify({"task_id": "string"})
});
const data = await resp.json();
console.log(data);
```

```go
package main

import (
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"task_id": "string"}`)
	req, _ := http.NewRequest("POST", "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/approve", body)
	req.Header.Set("Authorization", "Bearer <token>")
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
```

**Responses**

- `200` — Successful Response
- `422` — Validation Error

## `POST` /api/v1/factory/control/environments/{env_id}/reject

**Reject Card**

Stop a paused card — the worker fails it, blocking the dependent subtree (brand admin only).

**Parameters**

- `env_id` (path, string, required)

**Request body**

```bash
curl -X POST 'https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/reject' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "task_id": "string"
}'
```

```python
import httpx

resp = httpx.post(
    "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/reject",
    headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
    json={"task_id": "string"},
)
resp.raise_for_status()
print(resp.json())
```

```javascript
const resp = await fetch("https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/reject", {
  method: "POST",
  headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
  body: JSON.stringify({"task_id": "string"})
});
const data = await resp.json();
console.log(data);
```

```go
package main

import (
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"task_id": "string"}`)
	req, _ := http.NewRequest("POST", "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/reject", body)
	req.Header.Set("Authorization", "Bearer <token>")
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
```

**Responses**

- `200` — Successful Response
- `422` — Validation Error

## `POST` /api/v1/factory/control/environments

**Add Environment**

Add a factory env (brand admin). Creates the env record + reveals the one-shot install PAT/one-liner; the operator then runs the ansible provisioning playbook with it (whole-VPS bring-up is out-of-band — the api-gateway can't run ansible). Mirrors ``environments.py`` create + the same per-brand VPS quota gate.

**Request body**

```bash
curl -X POST 'https://app.opvs.ai/api/v1/factory/control/environments' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "string",
  "description": "string",
  "environment_type": "connected",
  "runtime": "claude_code",
  "workspace_path": "/home/agent",
  "sidecar_url": "string",
  "vps_provider": "string",
  "region": "string",
  "desk_kind": "dev_agent"
}'
```

```python
import httpx

resp = httpx.post(
    "https://app.opvs.ai/api/v1/factory/control/environments",
    headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
    json={"name": "string", "description": "string", "environment_type": "connected", "runtime": "claude_code", "workspace_path": "/home/agent", "sidecar_url": "string", "vps_provider": "string", "region": "string", "desk_kind": "dev_agent"},
)
resp.raise_for_status()
print(resp.json())
```

```javascript
const resp = await fetch("https://app.opvs.ai/api/v1/factory/control/environments", {
  method: "POST",
  headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
  body: JSON.stringify({"name": "string", "description": "string", "environment_type": "connected", "runtime": "claude_code", "workspace_path": "/home/agent", "sidecar_url": "string", "vps_provider": "string", "region": "string", "desk_kind": "dev_agent"})
});
const data = await resp.json();
console.log(data);
```

```go
package main

import (
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"name": "string", "description": "string", "environment_type": "connected", "runtime": "claude_code", "workspace_path": "/home/agent", "sidecar_url": "string", "vps_provider": "string", "region": "string", "desk_kind": "dev_agent"}`)
	req, _ := http.NewRequest("POST", "https://app.opvs.ai/api/v1/factory/control/environments", body)
	req.Header.Set("Authorization", "Bearer <token>")
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
```

**Responses**

- `201` — Successful Response
- `422` — Validation Error

## `DELETE` /api/v1/factory/control/environments/{env_id}

**Remove Environment**

Remove a factory env (brand admin). Deletes the env record + its PAT; decommissioning the VPS itself is an out-of-band ansible step.

**Parameters**

- `env_id` (path, string, required)

```bash
curl -X DELETE 'https://app.opvs.ai/api/v1/factory/control/environments/{env_id}' \
  -H 'Authorization: Bearer <token>'
```

```python
import httpx

resp = httpx.delete(
    "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}",
    headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())
```

```javascript
const resp = await fetch("https://app.opvs.ai/api/v1/factory/control/environments/{env_id}", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);
```

```go
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://app.opvs.ai/api/v1/factory/control/environments/{env_id}", nil)
	req.Header.Set("Authorization", "Bearer <token>")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
```

**Responses**

- `200` — Successful Response
- `422` — Validation Error

## Get Fleet Worker Config

`GET https://app.opvs.ai/api/v1/factory/control/worker-config`

Also as: MCP `tool_call software-factory.getFleetWorkerConfig` · skill `software-factory`

Every machine's switches in ONE call — the fleet switchboard's read.

| Code | When | What to do |
| --- | --- | --- |
| 422 | Validation Error |  |

## Get Worker Config

`GET https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config`

Also as: MCP `tool_call software-factory.getDeskWorkerConfig` · skill `software-factory`

Read an env's 5 operator switches (the dashboard panel's initial state).

| Code | When | What to do |
| --- | --- | --- |
| 422 | Validation Error |  |

## Set Worker Config

`PATCH https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/worker-config`

Also as: MCP `tool_call software-factory.setDeskWorkerConfig` · skill `software-factory`

Change an env's worker config and return its independently read state.

| Code | When | What to do |
| --- | --- | --- |
| 422 | Validation Error |  |

## Get Pending Cards

`GET https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/pending`

Also as: MCP `tool_call software-factory.factoryPendingCards` · skill `software-factory`

List an env's cards paused for human approval + its board-default mode.

| Code | When | What to do |
| --- | --- | --- |
| 422 | Validation Error |  |

## Approve Card

`POST https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/approve`

Also as: MCP `tool_call software-factory.factoryApproveCard` · skill `software-factory`

Release a paused card so its build proceeds (brand admin only).

| Code | When | What to do |
| --- | --- | --- |
| 422 | Validation Error |  |

## Reject Card

`POST https://app.opvs.ai/api/v1/factory/control/environments/{env_id}/reject`

Also as: MCP `tool_call software-factory.factoryRejectCard` · skill `software-factory`

Stop a paused card — the worker fails it, blocking the dependent subtree
(brand admin only).

| Code | When | What to do |
| --- | --- | --- |
| 422 | Validation Error |  |

## Add Environment

`POST https://app.opvs.ai/api/v1/factory/control/environments`

Also as: MCP `tool_call software-factory.factoryAddEnvironment` · skill `software-factory`

Add a factory env (brand admin). Creates the env record + reveals the
one-shot install PAT/one-liner; the operator then runs the ansible
provisioning playbook with it (whole-VPS bring-up is out-of-band — the
api-gateway can't run ansible). Mirrors ``environments.py`` create + the
same per-brand VPS quota gate.

| Code | When | What to do |
| --- | --- | --- |
| 422 | Validation Error |  |

## Remove Environment

`DELETE https://app.opvs.ai/api/v1/factory/control/environments/{env_id}`

Also as: MCP `tool_call software-factory.factoryRemoveEnvironment` · skill `software-factory`

Remove a factory env (brand admin). Deletes the env record + its PAT;
decommissioning the VPS itself is an out-of-band ansible step.

| Code | When | What to do |
| --- | --- | --- |
| 422 | Validation Error |  |
