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

# Shared-agent routing

> The organization switch and each public agent's approvers and notification recipients: read and write routes, compare-and-set saves, and the error codes

These routes configure [approvers and notification recipients for shared agents](/concepts/shared-agent-approvers). The organization switch turns the feature on for an organization. The agent settings name who approves and who is notified for one agent. The base URL, bearer authentication, and the error envelope are on the [REST API overview](/reference/rest/overview). The desktop app and the admin dashboard edit both the organization switch and the agent settings; see [Turning it on](/concepts/shared-agent-approvers#turning-it-on).

## Scopes

| Route                                         | Scope                            |
| --------------------------------------------- | -------------------------------- |
| `GET /admin/orgs/:orgId/shared-agent-routing` | `org:read` on the organization   |
| `PUT /admin/orgs/:orgId/shared-agent-routing` | `org:manage` on the organization |
| `GET /admin/agents/:agentId/routing`          | `agents:read` on the agent       |
| `PUT /admin/agents/:agentId/routing`          | `agents:manage` on the agent     |

A credential without the scope answers `403`.

The routing errors below use one envelope, with any extra fields at the top level:

```json theme={null}
{ "statusCode": 409, "error": "Conflict", "code": "ROUTING_REV_CONFLICT", "message": "Routing settings were changed by someone else; reload and try again", "currentRev": 3 }
```

## Organization switch

The switch is off for every organization until an admin turns it on. While it is off, agent settings can be read but not saved, and nothing is sent to approvers or recipients.

### GET /admin/orgs/:orgId/shared-agent-routing

Reads the switch.

**Response**

`200` with `{ "enabled": boolean, "killSwitch": boolean }`. `enabled` is the organization's own setting. `killSwitch` is `true` when Lua has turned the feature off for every organization; the organization's setting then has no effect until it is lifted.

### PUT /admin/orgs/:orgId/shared-agent-routing

Turns the switch on or off. The change is recorded in the organization's [audit log](/concepts/organizations-and-roles#what-an-administrator-controls).

<ParamField body="enabled" type="boolean" required>`true` to turn the feature on, `false` to turn it off.</ParamField>

**Response**

`200` with `{ "enabled", "killSwitch" }`.

**Errors**

| Status | Meaning                                           |
| ------ | ------------------------------------------------- |
| `400`  | `enabled` is missing or not a boolean             |
| `403`  | The caller lacks `org:manage` on the organization |

<CodeGroup>
  ```ts TypeScript theme={null}
  const response = await fetch('https://api.heylua.ai/admin/orgs/<<YOUR_ORG_ID>>/shared-agent-routing', {
    method: 'PUT',
    headers: { Authorization: 'Bearer <<YOUR_API_KEY>>', 'Content-Type': 'application/json' },
    body: JSON.stringify({ enabled: true }),
  });
  const flag: { enabled: boolean; killSwitch: boolean } = await response.json();
  console.log(flag.enabled, flag.killSwitch);
  ```

  ```bash cURL theme={null}
  curl -sS -X PUT "https://api.heylua.ai/admin/orgs/<<YOUR_ORG_ID>>/shared-agent-routing" \
    -H "Authorization: Bearer <<YOUR_API_KEY>>" \
    -H "Content-Type: application/json" \
    -d '{ "enabled": true }'
  ```
</CodeGroup>

## Agent settings

An agent's settings take effect only while the organization switch is on, the agent is public (shared with the organization), and it is not a [Space](/concepts/spaces). You can save them while the agent is private; they apply once it is shared.

### GET /admin/agents/:agentId/routing

Reads the agent's settings and the members who can be chosen.

**Response**

`200` with:

<ResponseField name="featureEnabled" type="boolean">The organization switch is on and the feature has not been turned off platform-wide.</ResponseField>
<ResponseField name="effective" type="boolean">The settings are in effect: `featureEnabled`, the agent is public and not a Space, and settings are saved.</ResponseField>

<ResponseField name="routing" type="object | null">
  The saved settings, or `null` when none have been saved.

  <Expandable title="properties">
    <ResponseField name="rev" type="number">The revision. Send it back on the next `PUT`.</ResponseField>
    <ResponseField name="approvers" type="object">`{ userIds, requireSomeoneElse, applyToWorkflowCreatorSteps }`, as on `PUT`.</ResponseField>
    <ResponseField name="notify" type="object">`{ userIds, includeTriggerer }`, as on `PUT`.</ResponseField>
    <ResponseField name="updatedBy" type="string">The user id that saved this revision.</ResponseField>
    <ResponseField name="updatedAt" type="string">When this revision was saved, ISO 8601.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="eligible" type="object[]">
  The organization's members, each `{ userId, name, email?, avatarUrl?, canApprove, canBeNotified, reason? }`. Empty when `featureEnabled` is `false`.

  <Expandable title="properties">
    <ResponseField name="canApprove" type="boolean">An active member with `operator` or above on the agent.</ResponseField>
    <ResponseField name="canBeNotified" type="boolean">An active member.</ResponseField>
    <ResponseField name="reason" type="string">Why a member can't be chosen: `deactivated` or `cannot_approve`.</ResponseField>
  </Expandable>
</ResponseField>

What `eligible` contains depends on the caller:

| Caller                                | `eligible`                                                                                 |
| ------------------------------------- | ------------------------------------------------------------------------------------------ |
| Can list the organization's members   | Every member, with `email`                                                                 |
| Holds `agents:manage` on the agent    | Every member, without `email`                                                              |
| Holds only `agents:read` on the agent | Only the chosen approvers and recipients, as `{ userId, name, canApprove, canBeNotified }` |

```json theme={null}
{
  "featureEnabled": true,
  "effective": true,
  "routing": {
    "rev": 3,
    "approvers": { "userIds": ["usr_priya", "usr_sam"], "requireSomeoneElse": true, "applyToWorkflowCreatorSteps": true },
    "notify": { "userIds": ["usr_ops"], "includeTriggerer": false },
    "updatedBy": "usr_priya",
    "updatedAt": "2026-09-28T09:14:02.000Z"
  },
  "eligible": [
    { "userId": "usr_priya", "name": "Priya Shah", "canApprove": true, "canBeNotified": true },
    { "userId": "usr_sam", "name": "Sam Lee", "canApprove": true, "canBeNotified": true },
    { "userId": "usr_ops", "name": "Ops Desk", "canApprove": false, "canBeNotified": true, "reason": "cannot_approve" }
  ]
}
```

### PUT /admin/agents/:agentId/routing

Replaces the agent's settings. The save is a compare-and-set on `rev`: send `0` when no settings have been saved, otherwise the `rev` you last read. A successful save increments `rev`. Pending approval requests follow the new settings: added approvers receive them, removed approvers' copies are withdrawn, and other copies are left as they are. The change is recorded in the organization's audit log.

<ParamField body="rev" type="number" required>`0` the first time, then the `rev` you last read. A stale value answers `409 ROUTING_REV_CONFLICT`.</ParamField>
<ParamField body="approvers.userIds" type="string[]" required>Up to 20 user ids. Each must be an active member with `operator` or above on the agent.</ParamField>
<ParamField body="approvers.requireSomeoneElse" type="boolean" required>The person who started a run or triggered a request can't decide it and gets no copy.</ParamField>
<ParamField body="approvers.applyToWorkflowCreatorSteps" type="boolean" required>Send workflow approval steps whose approver is `'creator'` to the approvers as well as the person who started the run. The desktop app turns it on by default.</ParamField>
<ParamField body="notify.userIds" type="string[]" required>Up to 50 user ids. Each must be an active member of the organization.</ParamField>
<ParamField body="notify.includeTriggerer" type="boolean" required>Also send notices to the person who triggered the work, when there is one.</ParamField>

**Response**

`200` with the same shape as `GET`, carrying the new `rev`.

**Errors**

| Status | Code                            | Meaning                                                                                                                                           |
| ------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | —                               | The body is malformed, or a list is over its cap (20 approvers, 50 recipients); `message` says which                                              |
| `403`  | —                               | The caller lacks `agents:manage` on the agent                                                                                                     |
| `404`  | `SHARED_AGENT_ROUTING_DISABLED` | The organization switch is off                                                                                                                    |
| `404`  | —                               | No such agent                                                                                                                                     |
| `409`  | `ROUTING_REV_CONFLICT`          | Someone saved since you read. `currentRev` is the saved revision (`null` if the settings were removed); read again and retry                      |
| `422`  | `ROUTING_INELIGIBLE_USERS`      | Someone on a list can't be chosen. `users` lists each as `{ userId, reason }`, with `reason` one of `not_member`, `deactivated`, `cannot_approve` |

```json theme={null}
{
  "statusCode": 422,
  "error": "Unprocessable Entity",
  "code": "ROUTING_INELIGIBLE_USERS",
  "message": "Some users cannot be added to this agent’s routing",
  "users": [{ "userId": "usr_ops", "reason": "cannot_approve" }]
}
```

<CodeGroup>
  ```ts TypeScript theme={null}
  const base = 'https://api.heylua.ai/admin/agents/<<YOUR_AGENT_ID>>/routing';
  const headers = { Authorization: 'Bearer <<YOUR_API_KEY>>', 'Content-Type': 'application/json' };

  const current: { routing: { rev: number } | null } = await (await fetch(base, { headers })).json();

  const response = await fetch(base, {
    method: 'PUT',
    headers,
    body: JSON.stringify({
      rev: current.routing?.rev ?? 0,
      approvers: { userIds: ['usr_priya', 'usr_sam'], requireSomeoneElse: true, applyToWorkflowCreatorSteps: true },
      notify: { userIds: ['usr_ops'], includeTriggerer: false },
    }),
  });
  if (response.status === 409) {
    const { currentRev } = await response.json();
    console.warn(`Saved by someone else (rev ${currentRev}); read again and retry.`);
  } else {
    const saved: { effective: boolean; routing: { rev: number } } = await response.json();
    console.log(saved.routing.rev, saved.effective);
  }
  ```

  ```bash cURL theme={null}
  curl -sS -X PUT "https://api.heylua.ai/admin/agents/<<YOUR_AGENT_ID>>/routing" \
    -H "Authorization: Bearer <<YOUR_API_KEY>>" \
    -H "Content-Type: application/json" \
    -d '{
      "rev": 0,
      "approvers": { "userIds": ["usr_priya", "usr_sam"], "requireSomeoneElse": true, "applyToWorkflowCreatorSteps": true },
      "notify": { "userIds": ["usr_ops"], "includeTriggerer": false }
    }'
  ```
</CodeGroup>

## See also

* [Approvers and notifications for shared agents](/concepts/shared-agent-approvers): who receives what, and what stays personal
* [Workflow approvals](/reference/rest/workflow-approvals): deciding an approval, and the no-op a late decision gets
* [`User.Inbox`](/reference/sdk/inbox): notices and questions from your code
