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

# Send work heartbeat

> Extend a self-hosted work item lease.

`POST /api/v1/cloud/environments/{environment_id}/work/{work_id}/heartbeat`

Sends a heartbeat for a work item and extends the worker lease. The first successful heartbeat moves a `starting` item to `active`.

Use `expected_last_heartbeat` for optimistic lease ownership. If another worker has taken over the item, the precondition fails with 412.

## Path parameters

| Parameter        | Type   | Description                           |
| ---------------- | ------ | ------------------------------------- |
| `environment_id` | string | Environment ID with the `env_` prefix |
| `work_id`        | string | Work item ID with the `work_` prefix  |

## Headers

| Header          | Required | Description         |
| --------------- | -------- | ------------------- |
| `Authorization` | Yes      | `Bearer $QODER_PAT` |

## Query parameters

| Parameter                 | Type    | Required | Description                                                                                                    |
| ------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `expected_last_heartbeat` | string  | No       | Either `NO_HEARTBEAT` or the last heartbeat timestamp returned by the server. Omit for an unconditional update |
| `desired_ttl_seconds`     | integer | No       | Desired lease TTL. Must be positive; accepted values are clamped to the supported range of 10 to 600 seconds   |

## Example request

```bash theme={null}
curl -X POST "https://api.qoder.com/api/v1/cloud/environments/env_019e64e01a137caf953ac2ac7b42ec5c/work/work_019f3be4fd2475d9a784bf2c739e1194/heartbeat?expected_last_heartbeat=NO_HEARTBEAT&desired_ttl_seconds=60" \
  -H "Authorization: Bearer $QODER_PAT"
```

## Example response

**HTTP 200 OK**

```json theme={null}
{
  "type": "work_heartbeat",
  "last_heartbeat": "2026-07-01T08:15:06.120394Z",
  "lease_extended": true,
  "state": "active",
  "ttl_seconds": 60
}
```

## Response fields

Returns a [Work heartbeat object](/cloud-agents/api/environments/work/schemas#work-heartbeat-object).

## Errors

| HTTP | Type                        | Trigger                                                                       |
| ---- | --------------------------- | ----------------------------------------------------------------------------- |
| 400  | `invalid_request_error`     | `desired_ttl_seconds` is not a positive integer                               |
| 400  | `invalid_request_error`     | `expected_last_heartbeat` is neither `NO_HEARTBEAT` nor an RFC 3339 timestamp |
| 400  | `invalid_request_error`     | The Environment is not `self_hosted`                                          |
| 401  | `authentication_error`      | PAT invalid or expired                                                        |
| 403  | `permission_error`          | Not authorized for this operation                                             |
| 404  | `not_found_error`           | Environment or work item not found                                            |
| 409  | `invalid_request_error`     | Work item is `queued` or `stopped` and cannot be heartbeated                  |
| 412  | `precondition_failed_error` | `expected_last_heartbeat` does not match the current lease owner              |

See [Errors](/cloud-agents/api/conventions/errors) for the full error envelope.

## Related

<CardGroup cols={2}>
  <Card title="Cloud environment setup" icon="server" href="/cloud-agents/environments">
    Choose the container, network, and dependencies your agent runs in.
  </Card>
</CardGroup>
