# Set, change or remove the key's webhook

`PATCH /o/{org}/api-keys/{key}`

Points the `runFinished` webhook of every run this key starts at a
public https URL, or clears it with `null`. The signing secret
(`webhook_secret`, `whsec_…`) is returned once, when the key first gets
a URL and again on `rotate_secret: true`; changing the URL keeps it.
Only the key's creator may call this.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `org` | path | string | yes | The org's slug or id. A caller with no role in the org gets `404`. |
| `key` | path | string | yes | The key's `id` (`key_…`), not its secret. |

## Request body

- `webhook_url` (string | null, optional)
- `rotate_secret` (boolean, optional, default `false`)

## Example: set

```bash
curl -X PATCH "https://api.prix.dev/o/$RUSH_ORG/api-keys/$KEY_ID" \
  -H "Authorization: Bearer $RUSH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://hooks.acme.dev/rush"
  }'
```

```typescript
const org = process.env.RUSH_ORG;
const keyId = process.env.KEY_ID;

const res = await fetch(`https://api.prix.dev/o/${org}/api-keys/${keyId}`, {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RUSH_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    webhook_url: "https://hooks.acme.dev/rush",
  }),
});

console.log(res.status, await res.json());
```

```python
import os

import requests

org = os.environ["RUSH_ORG"]
key_id = os.environ["KEY_ID"]

res = requests.patch(
    f"https://api.prix.dev/o/{org}/api-keys/{key_id}",
    headers={"Authorization": f"Bearer {os.environ['RUSH_TOKEN']}"},
    json={
        "webhook_url": "https://hooks.acme.dev/rush",
    },
)

print(res.status_code, res.json())
```

## Example: rotate

```bash
curl -X PATCH "https://api.prix.dev/o/$RUSH_ORG/api-keys/$KEY_ID" \
  -H "Authorization: Bearer $RUSH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rotate_secret": true
  }'
```

```typescript
const org = process.env.RUSH_ORG;
const keyId = process.env.KEY_ID;

const res = await fetch(`https://api.prix.dev/o/${org}/api-keys/${keyId}`, {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RUSH_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    rotate_secret: true,
  }),
});

console.log(res.status, await res.json());
```

```python
import os

import requests

org = os.environ["RUSH_ORG"]
key_id = os.environ["KEY_ID"]

res = requests.patch(
    f"https://api.prix.dev/o/{org}/api-keys/{key_id}",
    headers={"Authorization": f"Bearer {os.environ['RUSH_TOKEN']}"},
    json={
        "rotate_secret": True,
    },
)

print(res.status_code, res.json())
```

## Example: clear

```bash
curl -X PATCH "https://api.prix.dev/o/$RUSH_ORG/api-keys/$KEY_ID" \
  -H "Authorization: Bearer $RUSH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": null
  }'
```

```typescript
const org = process.env.RUSH_ORG;
const keyId = process.env.KEY_ID;

const res = await fetch(`https://api.prix.dev/o/${org}/api-keys/${keyId}`, {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RUSH_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    webhook_url: null,
  }),
});

console.log(res.status, await res.json());
```

```python
import os

import requests

org = os.environ["RUSH_ORG"]
key_id = os.environ["KEY_ID"]

res = requests.patch(
    f"https://api.prix.dev/o/{org}/api-keys/{key_id}",
    headers={"Authorization": f"Bearer {os.environ['RUSH_TOKEN']}"},
    json={
        "webhook_url": None,
    },
)

print(res.status_code, res.json())
```

## Responses

### 200

The key; `webhook_secret` only when a secret was minted.

- `id` (string, required): `key_…`; use it in the key routes.
- `name` (string, required)
- `prefix` (string, required): The secret's first characters, to tell keys apart.
- `scopes` (array of string, required)
- `max_concurrent` (integer, required)
- `webhook_url` (string | null, required)
- `expires_at` (string | null, required)
- `created_by` (object, required)
  - `id` (string, required)
  - `email` (string | null, required)
- `last_used_at` (string | null, required)
- `created_at` (string, required)
- `secret` (string, optional): The key's secret. Only in the create and rotate responses; never shown again.
- `webhook_secret` (string, optional): The webhook signing secret. Only in the update response that minted it.

### 400

An unknown field, a URL that is not public https, or `rotate_secret` with no URL (`VALIDATION_ERROR`).

### 401

No bearer, an unknown, revoked or expired one, or an API key on a route keys cannot call.

### 404

No such resource for the caller, including one that exists in an org the caller cannot see.

## Example response (200)

```json
{
  "id": "key_Zq3T9vB1mN4pX8cR2wYe",
  "name": "ci-runner",
  "prefix": "rk_live_Q7f2",
  "scopes": [
    "runs:read",
    "runs:write"
  ],
  "max_concurrent": 4,
  "webhook_url": "https://hooks.acme.dev/rush",
  "expires_at": "2027-01-01T00:00:00.000Z",
  "created_by": {
    "id": "5b8e2c1d-3f4a-4b6c-9d7e-8f9a0b1c2d3e",
    "email": null
  },
  "last_used_at": "2026-10-06T15:20:41.000Z",
  "created_at": "2026-10-06T15:00:00.000Z",
  "webhook_secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```
