# Sites API

With the **Sites API** you manage sites entirely via the API — without the pvnode web app. This
is the programmatic variant of the concepts described under [Sites & Strings](/en/v2/api/sites).

:::note
Requires a plan with Sites API access. Without this access, the write endpoints respond with
**403**. Hobby users manage sites through the
[pvnode web app](https://pvnode.com/sites). You can see your entitlements on the [Usage & Limits](https://pvnode.com/usage) page.
:::

## Endpoints at a glance

| Action | Endpoint |
|---|---|
| Create a site | `POST /v2/sites` |
| List all sites | `GET /v2/sites` |
| Retrieve a site | `GET /v2/sites/{site_id}` |
| Update a site | `PATCH /v2/sites/{site_id}` |
| Delete a site | `DELETE /v2/sites/{site_id}` |
| Restore a deleted site | `POST /v2/sites/{site_id}/restore` |

The exact request/response schemas are in the [API Reference](/api).

## Create

`latitude` and `longitude` are required. `elevation`, `timezone` and the terrain horizon are
determined automatically. Without `strings`, a single default string is created.

```bash title="curl"
curl -X POST https://api.pvnode.com/v2/sites \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Roof South",
    "latitude": 48.8566,
    "longitude": 2.3522,
    "strings": [{"slope": 30, "orientation": 180, "power_kw": 10}]
  }'
```

## Update (PATCH)

Partial update — only the sent fields change. Note:

- **`strings` replaces the entire array.** Each string has a stable, server-assigned `id` —
  **echo it to keep a string** (and its data, e.g. measurements); omit it and the string is dropped;
  send a string with no `id` to add a new one. The forecast `strings` block carries this `id` as
  `string_id` (plus a positional `string_index`), so read the site and forecast from the same point
  in time.
- **A location change** (coordinates) triggers a re-determination of elevation, timezone and horizon
  and invalidates the forecast cache.
- Location changes are limited to **1 per 30 days** per site (plan-independent) → otherwise **429**.

```bash title="curl"
curl -X PATCH https://api.pvnode.com/v2/sites/{site_id} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"config": {"modules": {"technology": "topcon"}}}'
```

## Deletion & restore

`DELETE` marks the site as "pending deletion" (`deleted_at` set); permanent
removal happens **30 days later**. Within this window, `POST .../restore` restores the
site.

## Limits & errors

| Limit / error           | Behavior                                                                        |
|-------------------------|---------------------------------------------------------------------------------|
| Sites API access        | Without access → **403**.                                                       |
| Site limit              | Maximum number of sites (can be `unmetered`) → **403** when exceeded.           |
| Strings per site        | Maximum strings per site → **422** when exceeded.                               |
| Ephemeral sites         | Without this entitlement → **403**.                                             |
| Location change quota   | 1 per 30 days per site → **429**.                                               |
| Invalid timezone        | **422**.                                                                        |
| Site not found          | **404**.                                                                        |

The complete reference of all fields and error codes is in the [API Reference](/api).
