---
name: wisnots-netz
description: Integrate the Wisnots Netz API (netz-api.wisnots.com), which registers PV, storage, heat pumps, wallboxes and air conditioning with the responsible German grid operator. Use whenever code, configuration or documentation in this project talks to that API.
---

# Wisnots Netz API

Registering an installation with the responsible German grid operator, through one API. This skill is the workflow and the rules. The index is https://www.wisnots.com/netz-api/llms.txt and the whole contract is https://www.wisnots.com/netz-api/openapi.json: read the index before the first call, and the operation's schema and examples before writing a body. Examples are also served one per file under https://www.wisnots.com/netz-api/examples/.

## Workflow

1. **Key.** Read it from the `NETZ_API_KEY` environment variable. `wnz_test_` is the sandbox, `wnz_live_` is live, same host (https://netz-api.wisnots.com) and same calls; build and test on the sandbox and switch the key to go live. Never write a key into a file, a log, a test fixture or a commit. If there is no key, ask for one: the console at https://www.wisnots.com/netzanmeldung/portal mints them under Entwickler.
2. **Open the case.** One case per Anlagentyp: `POST /v1/anmeldungen/{typ}` with `pv`, `speicher`, `waermepumpe`, `ladeeinrichtung` or `klimaanlage`, and `intent: "draft"`. Fields the system already holds go in `declared`. Files and a pasted mail go as `multipart/form-data` to `POST /v1/anmeldungen/{typ}/upload`: one `file` part per document, `text` for the mail, `declared` as JSON text. Send an `Idempotency-Key`. The first call in a new integration is the fixture: `{"intent":"draft","material":[{"fixture":"wallbox-invoice-de"}]}` on `ladeeinrichtung`, which the sandbox reads without a model call.
3. **Wait for the reading.** Act on `case.extraction_completed` from a webhook (`POST /v1/webhooks`), or poll `GET /v1/anmeldungen/{id}` until `status` leaves `extracting`. Poll the case, not a material: `pending` and `reading` both mean wait. About a minute per one-page PDF, more for a scan. Verify `Wisnots-Signature` (HMAC-SHA256 over `t.rawBody` with the endpoint secret, reject `t` older than five minutes) before trusting a delivery, and key on `Wisnots-Event-Id` because delivery is at-least-once.
4. **Close the gaps.** `open[]` names what is missing, who is expected to know it (`owed_by`) and whether it blocks. `PATCH /v1/anmeldungen/{id}` with `declared` for what the system or the user can answer; ask the customer for the rest, in the words of `label` and `help`.
5. **File.** `POST /v1/anmeldungen/{id}/file`, with an `Idempotency-Key`. On the sandbox choose `sandbox_outcome` and `sandbox_pace: "staged"` to rehearse `submitted`, `waiting_operator` and the answer as three webhooks. `signature` on the case says where the customer's signature stands.

## Rules

- Never invent a field or a value. `GET /v1/anmeldungen/{typ}/fields` is the dictionary: path, number (`nr`), label, help text, type, options, when it is required, who knows it. Build forms and mappings from it; a path not in it is refused.
- A typed route pins `device.category`; do not send it there. `14a` is the only route where it is declared.
- JSON goes to the create route and `/material`; files go only to `/upload`. A body on the wrong route is a 415 that names the right one.
- Declared values are authoritative and extraction never overwrites them. A disagreement is an entry in `conflicts`, resolved with a `PATCH`, never guessed.
- A refusal is a problem document with `code` (closed enum), `detail` and `errors[]` (`path`, `code`, `message`). Show `errors[]` to the person; do not retry a 4xx unchanged. A 429 carries `Retry-After`.
- One case per Anlagentyp. PV plus a battery is two cases with the same `external_ref`, which is echoed everywhere.
- Ids carry the plane, `anm_` live and `anm_test_` sandbox, and a key of one plane cannot touch the other's cases. Do not build a plane switch of your own: the key is the switch.
- Fixtures on the sandbox are free and identical every time; use them in automated tests. Real documents on the sandbox are read for real, 25 a day per account; live has no cap.
- More material may be added to any unfiled case, whatever its status, `ready` included; the case is resolved again. A filed case answers 409.
- Do not send a live key from a browser, and do not put the API behind a proxy that logs bodies: material carries customer documents.

## Routes

- `GET /v1/netzbetreiber`: Which grid operators, and which one here
- `POST /v1/webhooks`: Register an endpoint
- `GET /v1/webhooks`: Your endpoints
- `DELETE /v1/webhooks/{webhookId}`: Remove an endpoint
- `POST /v1/anmeldungen/pv`: Register a PV array
- `POST /v1/anmeldungen/pv/upload`: Upload files for a PV array
- `GET /v1/anmeldungen/pv/fields`: The PV field dictionary
- `POST /v1/anmeldungen/speicher`: Register a battery storage unit
- `POST /v1/anmeldungen/speicher/upload`: Upload files for a battery storage unit
- `GET /v1/anmeldungen/speicher/fields`: Fields for a battery storage unit
- `POST /v1/anmeldungen/waermepumpe`: Register a heat pump
- `POST /v1/anmeldungen/waermepumpe/upload`: Upload files for a heat pump
- `GET /v1/anmeldungen/waermepumpe/fields`: Fields for a heat pump
- `POST /v1/anmeldungen/ladeeinrichtung`: Register a charge point
- `POST /v1/anmeldungen/ladeeinrichtung/upload`: Upload files for a charge point
- `GET /v1/anmeldungen/ladeeinrichtung/fields`: Fields for a charge point
- `POST /v1/anmeldungen/klimaanlage`: Register an air conditioning unit
- `POST /v1/anmeldungen/klimaanlage/upload`: Upload files for an air conditioning unit
- `GET /v1/anmeldungen/klimaanlage/fields`: Fields for an air conditioning unit
- `POST /v1/anmeldungen/14a`: Create a § 14a registration
- `POST /v1/anmeldungen/14a/upload`: Upload files for a § 14a registration
- `GET /v1/anmeldungen/14a/fields`: The field dictionary
- `GET /v1/anmeldungen`: List your cases
- `GET /v1/anmeldungen/{id}`: Read a case
- `PATCH /v1/anmeldungen/{id}`: Declare or correct values
- `DELETE /v1/anmeldungen/{id}`: Discard a draft
- `POST /v1/anmeldungen/{id}/material`: Add raw material
- `POST /v1/anmeldungen/{id}/upload`: Upload files to a case
- `POST /v1/anmeldungen/{id}/documents`: Get an upload target
- `POST /v1/anmeldungen/{id}/file`: File the registration
- `GET /v1/anmeldungen/{id}/events`: Replay the log
- `GET /v1/anmeldungen/{id}/information-requests/{requestId}`: Read an operator information request
- `POST /v1/anmeldungen/{id}/information-requests/{requestId}/responses`: Provide the requested information
- `POST /v1/anmeldungen/{id}/information-requests/{requestId}/responses/upload`: Provide requested documents
- `POST /v1/anmeldungen/{id}/information-requests/{requestId}/responses/{responseId}/submit`: Submit a completed response

## TypeScript

A dependency-free client is served at https://www.wisnots.com/netz-api/sdk/: `index.ts`, `client.ts`, `types.gen.ts`, `webhooks.ts`, `README.md`. Vendor the four `.ts` files into the project; `README.md` is the guide. `createNetzClient({ apiKey })` gives `cases.create`, `cases.createFromFiles`, `cases.upload`, `cases.waitForExtraction`, `cases.patch`, `cases.file`, `cases.events`, `fields.get`, `operators.byPostcode` and `webhooks.create`; `verifyWebhookSignature` checks a delivery; every refusal is a `NetzApiError` with the problem's `code` and `errors`. In any other language, plain HTTP against the OpenAPI document is the whole integration.
