> ## Documentation Index
> Fetch the complete documentation index at: https://www.plungeai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Companies

> A company is a campus. You describe it in YAML documents, save them like any workflow, and apply them. The platform compiles them into a plan and runs it: it puts items (a feature, a brief, a piece of content) through each building's offices in order, and it stops at gates where a person …


A **company** is a campus. You describe it in YAML documents, save them like any workflow, and **apply**
them. The platform compiles them into a plan and runs it: it puts **items** (a feature, a brief, a piece of
content) through each building's offices in order, and it stops at **gates** where a person decides. This
chapter is the REST surface for all of it. Base URL is `https://api.plungeai.com`; every route takes an `ozk_`
key ([Authentication](/one-api/authentication)).

The same rules as [Bots](/one-api/bots) hold on every route here:

- **Your rows only.** The key's account is the tenant. A campus another account owns answers the same
  `404 company_not_found` as one that does not exist.
- **Four wires.** JSON by default. `POST`/`PUT` bodies may be YAML, and `format=` or `Accept` picks the
  response format (`json`, `yaml`, `markdown`, `text`). The event feed also streams as server-sent events.
- **One error shape.** `{"error": {"code", "message"}}` plus the `X-Error-Code` header
  ([Errors, limits & headers](/one-api/errors-limits)).

| Method and path | What it does |
|---|---|
| `GET /v1/companies` | your applied campuses: mode, status, building count, approvals waiting |
| `POST /v1/companies/{campus}/apply` | compile the saved documents into the stored plan |
| `GET /v1/companies/{campus}/layout` | the compiled company: buildings, policy, floors, intake, pipeline, offices |
| `GET /v1/companies/{campus}/snapshot` | the company now: office states, active items, the line at the door, spend today |
| `GET /v1/companies/{campus}/events` | the event log: a JSON page after `since`, or a live SSE feed (`Accept: text/event-stream` or `?stream=1`) |
| `POST /v1/companies/{campus}/events` | a local building's node reports its events up (`{events: [...]}`, 1 to 200) |
| `GET /v1/companies/{campus}/buildings/{building}/slice` | what a local building's node pulls to run it |
| `POST /v1/companies/{campus}/items` | put an item into a building: `{building, title, body?, kind?, priority?, tier?}` → `201` |
| `POST /v1/companies/{campus}/items/{id}/pause` · `resume` · `retry` · `cancel` · `steer` | act on one item (`steer` takes `{text}`) |
| `GET /v1/companies/{campus}/approvals` | the door: pending gates and tool permissions (`status=pending\|approved\|rejected\|expired`) |
| `POST /v1/companies/{campus}/approvals/{ap}` | decide: `{decision: approve\|reject, note?}` |
| `PUT /v1/companies/{campus}/mode` | `{mode: supervised\|tiered\|autonomous}`, live |
| `POST /v1/companies/{campus}/start` · `stop` | arm or disarm the campus clock; both idempotent |
| `DELETE /v1/companies/{campus}` | delete a stopped or never-started company and every row of it → `204` |

Source: `orchestration/api-gateway/routes/companies.ts` (the wire) over `orchestration/company-service/`
(every rule; `contract.ts` is its surface, `README.md` its grammar). Every response below comes from a live
walk on 2026-10-14 (UTC) against a throwaway campus that was deleted at the end. Ids are shown as `…`.

## 17.1 What a campus is

| Part | What it is |
|---|---|
| **Campus** | a document of `kind: campus`: a name, a slug, a mode and its buildings in map order |
| **Building** | a `kind: building` document, or a building written inline in the campus. It has floors of offices, a **standard** its policy follows, the intake items come through, and the **pipeline** an item walks |
| **Office** | an ordinary CNL workflow: the agents doing the work. It is a saved workflow name, or inline `{name, workflow}`. An inline office is saved for you on apply as `<campus>/<building>/<office>` |
| **Item** | one piece of work put into a building. It walks the pipeline step by step |
| **Gate** | a pipeline step where the standard's policy and the mode decide: pass it (`auto`) or ask a person (an **approval**) |

`location: cloud` buildings run their offices on the platform once the campus clock is started.
`location: local` buildings run on your own machine: the node pulls the building's **slice** and reports its
events up. The cloud clock never dispatches a local building's items.

The **mode** decides who passes a gate. `supervised` asks a person at every gate. `tiered` asks according to the
standard's tier table. `autonomous` asks only where a building's policy says `require`. There are two standards.
`code` (the software factory) has the gates `spec`, `build_permission`, `diff`, `merge` and `deploy`. `action`
(the venture studio) has the gates `brief`, `tool_permission`, `content`, `commitment` and `release`
(`orchestration/company-service/rubrics/`).

## 17.2 The documents: kinds `campus` and `building`

Documents are compiled, never executed. Save them with `POST /v1/workflows` and `kind: campus` or
`kind: building` ([16.1](/one-api/bots#161-lifecycle)). The walk below used one campus document with its building inline:
a cloud building whose pipeline starts with a gate, so nothing ran and nothing was spent.

```yaml
kind: campus
campus:
  name: plan-docs-…
  slug: plan-docs-…
  mode: supervised
  buildings:
    - name: Desk
      location: cloud
      policy:
        tiers: action
      floors:
        - name: "1"
          offices:
            - name: draft
              workflow:
                description: "Desk: draft one line from the item."
                tasks:
                  - type: harness
                    id: writer
                    agent: one-agent
                    goal: "Draft one line from this item. {input}"
                    mission: "You are the desk's writer."
                    allowed_tools: [task_complete]
                    max_iterations: 2
      intake:
        - api: true
      pipeline:
        - gate: brief
        - office: draft
```

| Key | Level | Meaning |
|---|---|---|
| `campus.name` · `campus.slug` | campus | the slug is the `{campus}` of every route (`^[a-z0-9][a-z0-9-]{0,62}$`). It defaults to the slugified name |
| `campus.buildings[]` | campus | building slugs in map order (each a `kind: building` document), or inline building bodies |
| `mode` · `manager` | campus, building | who decides at the doors (`user:<id>` or `team:<id>`); a building overrides the campus |
| `building.campus` | building document | the campus slug it stands on; it must equal the campus that lists it |
| `location` · `project` | building | `cloud` or `local`; `project` is for local buildings only |
| `policy.tiers` · `policy.gates` · `policy.budget` | building | the standard (`code` or `action`), a per-gate `require` / `auto` / `tiered`, and `day_usd` / `item_usd` hard stops |
| `floors[].offices[]` | building | office names (saved workflows, runner words in a local building, or inline `{name, workflow}`) |
| `intake[]` | building | `{api: true}` (the items route), `{bot: <saved bot>}`, `{schedule: <cron>, office: <office>}` |
| `pipeline[]` | building | in order: `{office}`, `{type: parallel\|sequential, id, offices}`, `{gate}`, `{then: <building>}` (last only) |

A document may not name machines or credentials: `node`, `path`, `bindings`, `api_key`, `token`, `secret`,
`service`, `worker`, `url`, `endpoint` and the like are refused, and an unknown key is refused with its path. The
full grammar is `orchestration/company-service/README.md`.

```bash
curl -s -X POST https://api.plungeai.com/v1/workflows \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d "$(jq -n --arg y "$(cat campus.yaml)" '{name:"plan-docs-…", kind:"campus", yaml:$y}')"
# → 201 {"workflow":{"id":"…","name":"plan-docs-…","kind":"campus","description":null,"folder":null,"updated_at":…,"version":null}}
```

## 17.3 Apply

Apply reads your saved documents, compiles them and stores the plan. It also saves any inline office as a
workflow. Apply again after you edit a document; an inline office the new plan no longer names is deleted.

```bash
curl -s -X POST https://api.plungeai.com/v1/companies/plan-docs-…/apply \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 200 {"campus":"plan-docs-…","buildings":[{"slug":"desk","location":"cloud","offices":1}],
#        "workflows":[{"name":"plan-docs-…/desk/draft","id":"…","action":"created"}],
#        "schedules":[],"local":[],"warnings":[]}
```

A document set that does not compile is `422 compile_failed`, with every `path: message` in the message. No
saved campus document with that slug is `404 company_not_found`. An inline office whose saved name
(`<campus>/<building>/<office>`) is already taken by an agent or a bot is `409 office_conflict`
(`orchestration/company-service/docs.ts`).

List and layout are read-only:

```bash
curl -s https://api.plungeai.com/v1/companies -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 200 {"companies":[…,{"slug":"plan-docs-…","name":"plan-docs-…","mode":"supervised","status":"applied",
#        "applied_at":"2026-10-14T00:29:15.962Z","buildings":1,"pending":0},…]}
```

```bash
curl -s https://api.plungeai.com/v1/companies/plan-docs-…/layout -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 200 {"campus":{"slug":"plan-docs-…","mode":"supervised","manager":null,…},
#        "buildings":[{"slug":"desk","location":"cloud","mode":"supervised",
#          "policy":{"rubric":"action","gates":{"brief":"tiered","tool_permission":"tiered","content":"tiered",
#                    "commitment":"tiered","release":"tiered"},"budget":{"day_usd":null,"item_usd":null}},
#          "intake":[{"api":true}],
#          "pipeline":[{"id":"gate:brief","kind":"gate","gate":"brief"},
#                      {"id":"draft","kind":"office","offices":["draft"],"retry":null,"skip":null}],
#          "connected":true,…}],
#        "offices":[{"building":"desk","slug":"draft","name":"plan-docs-…/desk/draft","floor":"1","order":0,"source":"inline"}]}
```

## 17.4 Start

```bash
curl -s -X POST https://api.plungeai.com/v1/companies/plan-docs-…/start -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 200 {"status":"running"}
```

Once the clock is started, cloud buildings walk their items. An office step starts an engine run, which is
paid like any other run. `stop` disarms the clock; items keep their step and wait (17.7).

## 17.5 Items

```bash
curl -s -X POST https://api.plungeai.com/v1/companies/plan-docs-…/items \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"building":"desk","title":"Launch note","body":"One line for the launch.","kind":"content"}'
# → 201 {"id":"…","campus":"plan-docs-…","building":"desk","title":"Launch note","body":"One line for the launch.",
#        "kind":"content","source":"api","priority":0,"tier":null,"step":"intake","attempt":0,"status":"active",
#        "parked_reason":null,"paused":0,"steer":null,"last_output":null,"cost_usd":0,"parent_item_id":null,…}
```

| Field | Rule |
|---|---|
| `building` | a building slug of this campus (required) |
| `title` | 1 to 300 characters (required) |
| `body` | text, up to 65,536 characters |
| `kind` | `feature`, `bugfix`, `docs`, `research`, `content` or `task` |
| `priority` | a whole number |
| `tier` | a tier word of the standard (`T0`…`T3`, `V0`…`V3`), up to 32 characters; without it the standard's default |

Any other field is `422 invalid_params` (`unknown field: …`). The snapshot shows where the item is. In the
walk below it is waiting at the gate, and its approval is in the `queue`:

```bash
curl -s https://api.plungeai.com/v1/companies/plan-docs-…/snapshot -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 200 {"head_seq":4,"mode":"supervised","status":"running",
#        "buildings":[{"slug":"desk","location":"cloud","connected":true,"mode":"supervised","active":1}],
#        "offices":[{"building":"desk","office":"draft","state":"idle","item_id":null}],
#        "items":[{"id":"…","title":"Launch note","step":"gate:brief","attempt":0,"status":"active",…}],
#        "queue":[{"id":"…","item_id":"…","step":"gate:brief","gate":"brief","tier":"V2","status":"pending",…}],
#        "truncated":false,"spend_today_usd":0,"shipped_7d":0}
```

The five item actions are `POST …/items/{id}/<action>`. Each answers the item as it is now:

| Action | Effect |
|---|---|
| `pause` | the item keeps its step and is not walked until resumed |
| `resume` | a paused or parked item goes on at the step it stopped on (a parked item starts a fresh visit) |
| `retry` | the step starts again as `attempt + 1`, or at the pipeline's retry target |
| `cancel` | the item ends `failed` with `parked_reason: cancelled`; running office runs are not interrupted |
| `steer` | `{text}` (1 to 2,000 characters after trimming): a note the next office run receives once. A blank or longer `text` is `422 invalid_params` |

A cancelled item (`status: failed`) refuses `pause` and `resume` with `409 item_failed`; `retry` it instead.
Any action on a finished item is `409 item_done` (`orchestration/company-service/company-service.ts`).
This walk did not call the five actions. The live oracle `tests/live/companies/items-actions.mjs` runs all
five against the deployed API on a local building of its own.

## 17.6 Approvals: the door

When an item reaches a gate that asks a person, the gate opens an approval. In `supervised` mode every gate
asks.

```bash
curl -s "https://api.plungeai.com/v1/companies/plan-docs-…/approvals?status=pending" \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 200 {"approvals":[{"id":"…","item_id":"…","building":"desk","step":"gate:brief","gate":"brief","tier":"V2",
#        "status":"pending","payload_json":"{\"summary\":\"brief: Launch note\"}","execution_id":null,
#        "requested_at":"2026-10-14T00:29:18.611Z","expires_at":"2026-10-16T00:29:18.304Z",
#        "decided_at":null,"decided_by":null,"note":null}]}
```

```bash
curl -s -X POST https://api.plungeai.com/v1/companies/plan-docs-…/approvals/… \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"decision":"reject","note":"Docs walk: nothing to run."}'
# → 200 {"approval":{"id":"…","item_id":"…","step":"gate:brief","gate":"brief","tier":"V2","status":"rejected",
#        "execution_id":null,"decided_at":"2026-10-14T00:29:19.573Z","decided_by":"user:…",
#        "note":"Docs walk: nothing to run.",…}}
```

`approve` moves the item on to the next step, and `reject` parks it (`parked_reason: rejected`). A gate
approval has `execution_id: null`. A **tool permission** is different: an office run paused to ask for a
tool. Approving it also continues that execution once and hands its result back to the item. The answer
then adds `execution_id` and `continued: true`, or `continued: false` with a `continue_error` (`not_paused`,
`session_expired`, `execution_not_found`, `continue_failed`). The decision stands either way. Rejecting a
tool permission leaves the run paused. A body other than `{decision: approve|reject, note?}` is
`422 invalid_params`.

## 17.7 Stop and delete

`DELETE` refuses a running company:

```bash
curl -s -X DELETE https://api.plungeai.com/v1/companies/plan-docs-… -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 409 {"error":{"code":"conflict","message":"company \"plan-docs-…\" is running — stop it first"}}
```

```bash
curl -s -X POST https://api.plungeai.com/v1/companies/plan-docs-…/stop -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 200 {"status":"stopped"}

curl -s -o /dev/null -w '%{http_code}\n' -X DELETE https://api.plungeai.com/v1/companies/plan-docs-… \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 204

curl -s https://api.plungeai.com/v1/companies/plan-docs-…/snapshot -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 404 {"error":{"code":"company_not_found","message":"no company \"plan-docs-…\""}}
```

Delete removes every company row at once: buildings, offices, items, runs, approvals, the event log and the
manifest. It does **not** remove your saved documents (the campus document and the inline offices saved as
`<campus>/<building>/<office>`). Find them with `GET /v1/workflows?search=<slug>` and delete each one with
`DELETE /v1/workflows/{id}` → `204` ([16.1](/one-api/bots#161-lifecycle)). The walk did this for both of its documents, and
afterwards the search listed none and `GET /v1/companies` no longer listed the campus.

## 17.8 Mode, events and local buildings

`PUT …/mode` takes `{mode}` and switches the whole campus live, from the next tick. It overrides the campus and
every building, a building's documented mode included, and it survives apply. Setting the campus's documented
mode clears the override. A missing or unknown mode is `422 invalid_params`. `GET …/events` answers a JSON page of the log
after `since` (a seq). With `Accept: text/event-stream` or `?stream=1` it is a live feed of `id: <seq>`,
`event: <type>`, `data: <row>`. A local building's node uses two more routes with the owner's key:
`GET …/buildings/{building}/slice`, which returns the building's compiled pipeline, policy, project and office
ids, and `POST …/events` (`{events: [{building, type, item_id?, office?, data?, at?}]}`, 1 to 200 events) to
report back. This walk did not call these four routes. Their rules are in `routes/companies.ts` and
`routes/companies-events.ts`.

## 17.9 Errors

| Status | Code | When |
|---|---|---|
| 400 | `invalid_format` | `format=` is not `json`, `yaml`, `markdown` or `text` |
| 404 | `company_not_found` | no campus with that slug for this key, or a slug that cannot be one (answered without a lookup) |
| 404 | `building_not_found` · `item_not_found` · `approval_not_found` | no such building, item or approval in this company |
| 409 | `conflict` | `DELETE` while the company runs: stop it first |
| 409 | `item_failed` · `item_done` | `pause` or `resume` of a cancelled item (retry it); any action on a finished item |
| 409 | `approval_not_pending` | deciding an approval that is already decided or expired |
| 409 | `office_conflict` | apply: an inline office's saved name is taken by an agent or a bot |
| 422 | `invalid_params` | an item, decision, mode, steer or events body that breaks its rule; the message names the field |
| 422 | `compile_failed` | apply: the documents do not compile; the message lists each `path: message` |
| 503 | `not_enabled` | the deployment has no company service |

```bash
curl -s https://api.plungeai.com/v1/companies/no-such-campus/layout -H "Authorization: Bearer $PLUNGE_API_KEY"
# → 404 {"error":{"code":"company_not_found","message":"no company \"no-such-campus\""}}
```

```bash
curl -s -X POST https://api.plungeai.com/v1/companies/plan-docs-…/items \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' -d '{"building":"desk"}'
# → 422 {"error":{"code":"invalid_params","message":"title must be 1-300 characters"}}
```

The same operations are in the API reference under **Companies**, one page per route.
