# PATCH /v2/signals/{signal}

**Update a custom signal**

Partial update — supply `expr`, `description`, `name`, or any combination. Providing `expr` recompiles against the live column whitelist (same validator as create). Built-in signals are read-only — only custom signals you own can be patched.

**The name IS the signal's API handle.** Renaming via `name` changes what you reference in `q` and the CRUD path itself — old-name references in your code will stop compiling. Rename is refused with 409 `rename_blocked` while other custom signals of yours reference the current name (edit those first), and with 409 `name_collision` / `already_exists` if the new name hits a built-in column or an existing signal.

**Existing subscriptions do not change.** Scan/signal/ticker `subscribe` endpoints freeze a custom signal's SQL into the webhook at creation time, so editing (or renaming) the signal here updates ad-hoc reads and *new* subscriptions but leaves already-created webhooks firing on the old definition. Re-subscribe to apply the change.

## Plan access

- **Plan access.** Available on every plan, including Free — authoring and consuming both.
- **Rate limit.** Free 60/min · Hobby 600/min · Pro 6,000/min · Scale 60,000/min.
- **Capacity.** Unlimited on every plan.

## Body parameters

| Name | In | Type | Required | Description |
|------|----|----|----------|-------------|
| `signal` | path | string | yes | Custom signal slug (the signal name). |
| `expr` | body | string | no | New SQL expression. Re-validated and re-inlined against your other custom signals. |
| `description` | body | string | no | New description. |
| `name` | body | string | no | New slug — renames the signal and changes its API handle everywhere (same validation as create). Refused while other custom signals reference the current name. |

## Status codes

- **200** — `{ as_of, signal: {...} }` — the updated signal.
- **400** — `compile_failed` on bad expression, `bad_request` on shape failure.
- **404** — `not_found` — no custom signal with that name on this account (built-ins are not patchable).
- **409** — `rename_blocked` (other custom signals reference the current name — listed in `references.signals`), `name_collision` (new name is a built-in column), or `already_exists` (you already have a signal with the new name).

## Sample response

```json
{
  "as_of": "2026-05-30T18:55:00.000Z",
  "signal": {
    "name": "rsi_oversold_with_volume",
    "kind": "expression",
    "description": "RSI < 30 confirmed by a 2x relative-volume burst.",
    "expr": "rsi_14 < 30 AND volume_ratio_20d > 1.5",
    "created_at": 1779389700,
    "updated_at": 1779476400
  }
}
```

## Examples

### Loosen the volume threshold

Request:

```shell
curl -X PATCH "https://api.tickerbot.io/v2/signals/rsi_oversold_with_volume" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "expr": "rsi_14 < 30 AND volume_ratio_20d > 1.5" }'
```

Response (`200`):

```json
{
  "as_of": "2026-05-30T18:55:00.000Z",
  "signal": {
    "name": "rsi_oversold_with_volume",
    "kind": "expression",
    "description": "RSI < 30 confirmed by a 2x relative-volume burst.",
    "expr": "rsi_14 < 30 AND volume_ratio_20d > 1.5",
    "created_at": 1779389700,
    "updated_at": 1779476400
  }
}
```

---

Interactive sandbox + parameter editor: https://tickerbot.io/docs/endpoints/signals/custom/update
