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

# Hooks

> Lifecycle hook registry -- read + enable/disable only.

The lifecycle hook registry is **read + enable/disable only** over HTTP. Hooks fire shell commands, HTTP webhooks, or subagents on lifecycle events; the API lets operators list them and toggle them on or off, but registration itself is a config-time operation via `hooks.Register` in Go — there is no create or delete endpoint. See the [Hooks guide](/guides/hooks).

<Note>
  All endpoints require authentication and are workspace-scoped. Enable / disable additionally require `OWNER` or `ADMIN`.
</Note>

## Endpoints

| Method | Endpoint                                 | Purpose                                  |
| ------ | ---------------------------------------- | ---------------------------------------- |
| GET    | [`/api/v1/hooks`](#list-hooks)           | List hooks (optionally filtered by crew) |
| POST   | [`/api/v1/hooks/{id}/enable`](#enable)   | Enable a hook                            |
| POST   | [`/api/v1/hooks/{id}/disable`](#disable) | Disable a hook                           |

Registration is Go-only — see [Registration](#registration-go-only).

***

## Reading hooks

List the registered hooks for a workspace, optionally narrowed to one crew.

### List hooks

```
GET /api/v1/hooks?crew_id=<optional>
```

**Query parameters:**

| Param     | Type   | Description                                                                 |
| --------- | ------ | --------------------------------------------------------------------------- |
| `crew_id` | string | Optional. Filter to hooks targeting this crew. Cross-tenant IDs return 404. |

**Response:** `200 OK`

```json theme={null}
{
  "rows": [
    {
      "id": "hk_abc123",
      "workspace_id": "ws_123",
      "crew_id": "crw_backend",
      "event": "on_approval_requested",
      "handler_kind": "http",
      "handler_config": {
        "url": "https://hooks.slack.com/services/...",
        "method": "POST",
        "hmac_secret": "redacted_on_read"
      },
      "matcher": {
        "severities": ["warn", "error"]
      },
      "enabled": true,
      "blocking": false,
      "created_by": "user_123",
      "created_at": "2026-03-01T09:00:00Z",
      "updated_at": "2026-04-15T14:22:00Z"
    }
  ],
  "count": 1
}
```

| Field                   | Type    | Description                                                                                         |
| ----------------------- | ------- | --------------------------------------------------------------------------------------------------- |
| `rows[].event`          | string  | One of the 15 lifecycle events. See [Hooks guide](/guides/hooks#events).                            |
| `rows[].handler_kind`   | string  | `shell`, `http`, or `subagent`.                                                                     |
| `rows[].handler_config` | object  | Handler-specific config (URL for http, command for shell, subagent name for subagent).              |
| `rows[].matcher`        | object  | Optional filter: `tools`, `agent_ids`, `crew_ids`, `severities`. Empty = match-all.                 |
| `rows[].blocking`       | boolean | If true, a Block outcome short-circuits the triggering operation. Non-blocking runs in a goroutine. |

**Errors:**

| Status | Condition                                     |
| ------ | --------------------------------------------- |
| 401    | No workspace.                                 |
| 404    | `crew_id` filter points to cross-tenant crew. |
| 500    | DB error.                                     |

***

## Toggling hooks

Flip a hook on or off. Both transitions emit `system.hook_toggled` into the journal with the actor's user ID.

<Warning>
  Enabling a hook can invoke shell commands, hit third-party webhooks, or dispatch subagents -- which is why both endpoints require `OWNER` or `ADMIN`.
</Warning>

### Enable

```
POST /api/v1/hooks/{id}/enable
```

**Auth:** `OWNER` or `ADMIN` only (403 otherwise). Enabling a hook can invoke shell commands, hit third-party webhooks, or dispatch subagents -- the role bar matches that risk.

**Response:** `200 OK`

```json theme={null}
{ "id": "hk_abc123", "enabled": true }
```

**Errors:**

| Status | Condition                   |
| ------ | --------------------------- |
| 400    | Missing `id`.               |
| 401    | Not authenticated.          |
| 403    | Not OWNER/ADMIN.            |
| 404    | Hook not in your workspace. |
| 500    | DB error.                   |

Emits `system.hook_toggled` into the journal with the actor's user ID.

***

### Disable

```
POST /api/v1/hooks/{id}/disable
```

Same auth rules, same response shape with `"enabled": false`.

## Registration (Go only)

No HTTP endpoint. Call `hooks.Register` from workspace provisioning code:

```go theme={null}
const allowedShell = false // flip to true only when the caller has confirmed OWNER
_, err := hooks.Register(ctx, db, hooks.Hook{
    WorkspaceID: ws,
    Event:       hooks.EventOnApprovalRequested,
    HandlerKind: hooks.HandlerKindHTTP,
    HandlerConfig: map[string]any{
        "url": "https://...",
    },
    Enabled: true,
}, allowedShell)
```

The trailing `allowedShell bool` argument is the gate on `HandlerKindShell`: pass `false` for any non-OWNER context — the function returns `hooks.ErrShellHookNotAllowed` if the hook is a shell kind and this flag is `false`. HTTP / subagent hooks are unaffected by the flag.

## Related

* [Hooks guide](/guides/hooks).
* [`crewship hooks`](/cli/hooks).
