{
  "info": {
    "name": "AmpVerve Platform API",
    "description": "Telemetry ingestion, asset registry, market data and flexibility settlement for distributed energy resources.\n\nGenerated from the AmpVerve OpenAPI document version 2026-10-15 (https://app.ampverve.com/api-docs/openapi.json).\nIt holds every operation a partner API key (`avk_...`) can call and every public operation. Operations that take a user access token instead of an API key are left out because a key cannot call them.\n\nSet the `apiKey` collection variable to your key. It is sent as `Authorization: Bearer {{apiKey}}`. A sandbox key selects the sandbox on the same base URL. Public requests send no credential.\n\nEvery request description starts with the operation's status label (Live, Available after scheduled maintenance, Available on request or Demo environment). Documentation: https://www.ampverve.com/api-docs/",
    "version": "2026-10-15",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://app.ampverve.com",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Connect",
      "item": [
        {
          "name": "Home Assistant connector",
          "item": [
            {
              "name": "The confirmed entity mapping for a connector site [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/telemetry/connectors/home-assistant/v1/sites/:site_id/mapping",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "telemetry",
                    "connectors",
                    "home-assistant",
                    "v1",
                    "sites",
                    ":site_id",
                    "mapping"
                  ],
                  "variable": [
                    {
                      "key": "site_id",
                      "value": "",
                      "description": "Required. The site's identifier within the caller's organisation."
                    }
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key, no scope needed.\n**Reference:** https://www.ampverve.com/api-docs/#op-haconnectorgetmapping\n\nCalled by the AmpVerve Home Assistant connector running in a home. Scope\n`telemetry:write`. Returns the mapping a person confirmed for this site in the\nkey's organisation, or `status: unconfirmed` (a report exists, nothing is\nconfirmed) or `status: unknown` (no such site in the key's organisation). The\nconnector sends readings only for a confirmed mapping. Phase 1 is read only:\nnothing on this surface returns or queues a command."
              },
              "response": []
            },
            {
              "name": "Send the entity discovery and proposed mapping report [live]",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/telemetry/connectors/home-assistant/v1/sites/:site_id/report",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "telemetry",
                    "connectors",
                    "home-assistant",
                    "v1",
                    "sites",
                    ":site_id",
                    "report"
                  ],
                  "variable": [
                    {
                      "key": "site_id",
                      "value": "",
                      "description": "Required. The site's identifier within the caller's organisation."
                    }
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key, no scope needed.\n**Reference:** https://www.ampverve.com/api-docs/#op-haconnectorpostreport\n\nScope `telemetry:write`. The connector's discovery of the home's Home Assistant:\nentity ids, friendly names, domains, units, device and state classes and\nintegrations (never state or attribute values; people, trackers, zones and\ncameras are left out), keyword groups, a proposed mapping onto the normalised\nmodel, and a human readable rendering in `text`. Stored per site in the key's\norganisation. A body naming an organisation (`tenant_id`, `organisation_id`,\n`org_id`) is refused with 403 and nothing is stored.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"schema\": \"ampverve.home_assistant.mapping_report.v1\",\n  \"connector_version\": \"0.1.0\",\n  \"entity_count\": 1,\n  \"entities\": [\n    {\n      \"entity_id\": \"sensor.grid_power\",\n      \"domain\": \"sensor\",\n      \"unit_of_measurement\": \"kW\",\n      \"device_class\": \"power\",\n      \"state_class\": \"measurement\",\n      \"platform\": null,\n      \"available\": true,\n      \"numeric\": true,\n      \"attribute_keys\": [\n        \"unit_of_measurement\"\n      ]\n    }\n  ],\n  \"proposed_mapping\": [\n    {\n      \"metric\": \"site.grid.import_power_w\",\n      \"entity_id\": \"sensor.grid_power\",\n      \"transform\": \"positive_part\",\n      \"confidence\": \"medium\"\n    }\n  ],\n  \"text\": \"AmpVerve Home Assistant entity mapping report ...\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Push normalised readings and connector health [live]",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/telemetry/connectors/home-assistant/v1/sites/:site_id/telemetry",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "telemetry",
                    "connectors",
                    "home-assistant",
                    "v1",
                    "sites",
                    ":site_id",
                    "telemetry"
                  ],
                  "variable": [
                    {
                      "key": "site_id",
                      "value": "",
                      "description": "Required. The site's identifier within the caller's organisation."
                    }
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key, no scope needed.\n**Reference:** https://www.ampverve.com/api-docs/#op-haconnectorposttelemetry\n\nScope `telemetry:write`. Up to 2000 readings in the canonical units of the\nnormalised model (power W, energy kWh, prices GBP/kWh, temperatures degrees\nCelsius), plus a `connector` health block. Every reading's metric must be in the\nsite's confirmed mapping (409 when nothing is confirmed, 422 for a metric\noutside it). A reading with `quality` `unavailable` or `invalid` must have\n`value: null` and is stored with no value and quality `missing` or `invalid`,\nnever as zero. Readings are stored in the telemetry store under the key's\norganisation, as asset `ha:{site_id}:{metric}`. An empty `readings` list is a\nheartbeat. 200 means every reading is persisted; 503 means nothing (or not\neverything) was, and the connector keeps the batch and retries.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"connector\": {\n    \"connector_version\": \"0.1.0\",\n    \"ha_connected\": true,\n    \"entities_total\": 412,\n    \"entities_mapped\": 24,\n    \"unavailable_entities\": [],\n    \"buffered\": 0,\n    \"dropped\": 0,\n    \"control_enabled\": false\n  },\n  \"readings\": [\n    {\n      \"metric\": \"site.grid.import_power_w\",\n      \"value\": 0,\n      \"unit\": \"W\",\n      \"quality\": \"measured\",\n      \"ts\": \"2026-10-05T12:00:00Z\",\n      \"entity_id\": \"sensor.grid_power\"\n    },\n    {\n      \"metric\": \"battery.soc_percent\",\n      \"value\": null,\n      \"unit\": \"%\",\n      \"quality\": \"unavailable\",\n      \"ts\": \"2026-10-05T12:00:00Z\",\n      \"entity_id\": \"sensor.battery_soc\"\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ],
          "description": "The AmpVerve Home Assistant connector runs in a home next to Home Assistant and pushes\nnormalised telemetry with an organisation API key (scope `telemetry:write`). The\n`v1` operations are the connector's; the others are for people in the organisation\nto review the discovery report, confirm the mapping and watch connectivity health.\nPhase 1 is read only: no operation sends or queues a command."
        }
      ]
    },
    {
      "name": "Settle and report",
      "item": [
        {
          "name": "Carbon and emissions",
          "item": [
            {
              "name": "Current GB grid carbon intensity [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/trading/carbon-intensity",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "trading",
                    "carbon-intensity"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-getcarbonintensity\n\nLive national carbon intensity from National Grid ESO, in gCO2 per kWh, for the\ncurrent half hour settlement period.",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            },
            {
              "name": "Carbon intensity forecast [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/trading/carbon-intensity/forecast",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "trading",
                    "carbon-intensity",
                    "forecast"
                  ],
                  "query": [
                    {
                      "key": "hours",
                      "value": "24",
                      "description": "Range 1 to 48.",
                      "disabled": true
                    }
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-getcarbonforecast",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ],
          "description": "GB grid carbon intensity and its forecast (National Grid ESO), the stored\nhalf hourly intensity for a site's region, Scope 2 on both the location and\nmarket based methods, avoided emissions, emission factor versions and their\nchange log, and a household's import carbon footprint."
        },
        {
          "name": "Settlement",
          "item": [
            {
              "name": "Published receipt verification keys [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/vpp/vpp/settle/keys",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "vpp",
                    "vpp",
                    "settle",
                    "keys"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-listsettlesigningkeys\n\nEvery Ed25519 key that has ever signed a settlement receipt, by key id, with\nits status: `active`, `retired` (still verifies what it signed before its\nretirement time) or `revoked`. No authentication. A counterparty verifies a\nreceipt by SHA-256 of the canonical body bytes and an Ed25519 check of the\nsignature over the 32 digest bytes with `public_key_hex`.",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            },
            {
              "name": "A shared receipt, for counterparty verification [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/vpp/vpp/settle/public/receipts/:receipt_hash",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "vpp",
                    "vpp",
                    "settle",
                    "public",
                    "receipts",
                    ":receipt_hash"
                  ],
                  "variable": [
                    {
                      "key": "receipt_hash",
                      "value": "",
                      "description": "Required."
                    }
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-getsharedsettlereceipt\n\nA receipt its organisation chose to share, with its verification. No\nauthentication. An unknown, private or unshared hash all answer the same 404.\nRate limited per client.",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ],
          "description": "AmpVerve Settle. Ed25519 signed receipts for delivered flexibility, chained per\norganisation, verifiable by a counterparty from the published keys, and\nreconciled against the counterparty's settlement statement. Receipts state\nwhat was recorded; they move no money and submit nothing to any market."
        },
        {
          "name": "Fleet savings statements",
          "item": [
            {
              "name": "Check a savings statement by hash (public) [awaiting-migration]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/vpp/vpp/fleet-statements/public/verify/:statement_hash",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "vpp",
                    "vpp",
                    "fleet-statements",
                    "public",
                    "verify",
                    ":statement_hash"
                  ],
                  "variable": [
                    {
                      "key": "statement_hash",
                      "value": "",
                      "description": "Required."
                    }
                  ]
                },
                "description": "**Status: awaiting-migration** (Available after scheduled maintenance). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-verifyfleetsavingsstatementhash\n\nWhether AmpVerve issued a statement with this SHA-256 hash, whether it is signed or\nan UNSIGNED draft, when, and whether the signature verifies against the published\nkey. No authentication. Returns no organisation, site, session or figure. Rate\nlimited per client. An unknown hash answers 404.",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            },
            {
              "name": "Check a savings statement file (public) [awaiting-migration]",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/vpp/vpp/fleet-statements/public/verify",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "vpp",
                    "vpp",
                    "fleet-statements",
                    "public",
                    "verify"
                  ]
                },
                "description": "**Status: awaiting-migration** (Available after scheduled maintenance). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-verifypresentedfleetsavingsstatement\n\nVerifies the statement JSON a holder presents, byte for byte: its hash, its\ncanonical form, whether AmpVerve issued it, and its signature. No authentication.\nReturns the verification only, never any of the statement's content. Rate limited.",
                "auth": {
                  "type": "noauth"
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"body\": \"{\\\"baseline\\\":{...}}\",\n  \"signature\": \"77e38ac0...0f\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ],
          "description": "A signed monthly statement per fleet depot: charging cost against a baseline the\ncustomer agreed in advance, with the method, the data coverage and every excluded\nsession, signed with the settlement receipt key so a finance team can check it was\nnot changed. Public verification returns no customer data."
        }
      ]
    },
    {
      "name": "Commercial",
      "item": [
        {
          "name": "Lead intake",
          "item": [
            {
              "name": "Request a DepotTwin study from the Depot Capacity Check (no credential) [awaiting-migration]",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/notifications/intake/capacity-check",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "notifications",
                    "intake",
                    "capacity-check"
                  ]
                },
                "description": "**Status: awaiting-migration** (Available after scheduled maintenance). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-requestcapacitycheckstudy\n\nThe request form under https://app.ampverve.com/capacity-check. Public: no\ncredential and no organisation. The lead is written into the one organisation\nnamed by the deployment's `CAPACITY_CHECK_LEAD_ORG_ID`, never one the request\nnames; when that is unset, or that organisation does not have the `crm`\nmodule, the answer is 503 and nothing is written. One request records an\naccount, a contact (with the consent wording and time as its lawful basis), a\n`depottwin_study` deal at `lead` (an open one is reused), a note with the\nvisitor's inputs and the page's planning estimates, and a task for a person to\nreply. No email is sent automatically.\n\nThe contact, note and task are personal data under the CRM retention rule: a\ncontact with no engagement is erased with them after the organisation's\n`prospect_retention_days`. No IP address or browser data is stored.\n\nControls: `Content-Type: application/json` only (415); 8 KiB cap (413);\nunknown fields refused (422); `consent_contact` must be `true`; a UK depot\npostcode; a work email (consumer and disposable mailboxes are refused, 422);\na hidden honeypot field `website` (a filled one is answered 202 and nothing is\nwritten); `form_started_ms` at least 5 seconds before the request (429); rate\nlimits per IP (5 an hour, 20 a day), per email (3 a day) and in total (30 an\nhour, 200 a day), each 429. The same request from the same address within 10\nminutes is recorded once. The 202 answer is the same whether or not the address\nwas already known.",
                "auth": {
                  "type": "noauth"
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"company\": \"\",\n  \"name\": \"\",\n  \"email\": \"integration@example.com\",\n  \"depot_postcode\": \"ZZ99 3CZ\",\n  \"consent_contact\": true,\n  \"form_started_ms\": 0,\n  \"website\": \"\",\n  \"inputs\": {\n    \"kva\": 0,\n    \"day_load_kw\": 0,\n    \"night_load_kw\": 0,\n    \"vans\": 0,\n    \"arrive\": \"\",\n    \"depart\": \"\",\n    \"miles_per_day\": 0,\n    \"miles_per_kwh\": 0,\n    \"charger_kw\": 0,\n    \"day_rate_pence\": 0,\n    \"night_rate_pence\": 0,\n    \"operating_days\": 0,\n    \"power_factor\": 0\n  },\n  \"estimates\": {\n    \"connection_kw\": 0,\n    \"vans_fit_on_arrival\": 0,\n    \"vans_fit_smart\": 0,\n    \"peak_on_arrival_kw\": 0,\n    \"peak_smart_kw\": 0\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ],
          "description": "The public request form of the Depot Capacity Check. No credential: it\nrecords a request for a DepotTwin study as a CRM lead in the one\norganisation the deployment names, for a person to answer. It opens no\nother CRM operation."
        }
      ]
    },
    {
      "name": "Developer tools",
      "item": [
        {
          "name": "Partner API",
          "item": [
            {
              "name": "The organisation and key a partner API key resolves to [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/devtools/partner/v1/me",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "devtools",
                    "partner",
                    "v1",
                    "me"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key, no scope needed.\n**Reference:** https://www.ampverve.com/api-docs/#op-partnerwhoami\n\nAny valid key, no scope needed. Use it to check a key. The organisation is the\none the key was issued to; no header, parameter or body can change it.\n`environment` is `sandbox` for a sandbox key and `live` otherwise.\n`key.expires_in_days` and `key.expiring_soon` (true from\n`key.expiry_warning_days` before expiry) say when to rotate; every Partner\nAPI response also carries `AmpVerve-Key-Expires-At`, and\n`AmpVerve-Key-Expiry-Warning` once the key is expiring soon."
              },
              "response": []
            },
            {
              "name": "Alerts for the key's organisation [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/devtools/partner/v1/alerts",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "devtools",
                    "partner",
                    "v1",
                    "alerts"
                  ],
                  "query": [
                    {
                      "key": "status",
                      "value": "open",
                      "description": "One of: open, resolved, all.",
                      "disabled": true
                    },
                    {
                      "key": "since",
                      "value": "",
                      "description": "Only alerts created at or after this time. A filter. Moving `since` back to read older alerts was the way to page before `cursor`; that still works but is deprecated as a way to page. Use `cursor`.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "Page size. Default 50, at most 200. Range 1 to 200.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Opaque. The `next_cursor` of the previous page, sent unchanged; omit it for the first page. Send the same filters with every page. A cursor that cannot be read, that belongs to another operation, or that comes with different filters is `400` `invalid_cursor`. A cursor names no organisation and grants nothing: every page is read for your own organisation. See the Pagination page.",
                      "disabled": true
                    }
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key with scope `alerts:read`.\n**Reference:** https://www.ampverve.com/api-docs/#op-partnerlistalerts\n\nScope `alerts:read`. Identifiers, type, severity, title and times. Operator free\ntext (alert messages and context) is not exposed. A sandbox key gets the two\nsimulated alerts.\n\nPaginated with `limit` and `cursor`: newest first, ties on time broken by\n`id`. Send the same `status` and `since` with every page. See the Pagination\npage."
              },
              "response": []
            },
            {
              "name": "Devices registered to the key's organisation [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/devtools/partner/v1/devices",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "devtools",
                    "partner",
                    "v1",
                    "devices"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "100",
                      "description": "Page size. Default 100, at most 500. Range 1 to 500.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Opaque. The `next_cursor` of the previous page, sent unchanged; omit it for the first page. Send the same filters with every page. A cursor that cannot be read, that belongs to another operation, or that comes with different filters is `400` `invalid_cursor`. A cursor names no organisation and grants nothing: every page is read for your own organisation. See the Pagination page.",
                      "disabled": true
                    }
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key with scope `devices:read`.\n**Reference:** https://www.ampverve.com/api-docs/#op-partnerlistdevices\n\nScope `devices:read`. A sandbox key gets the simulated devices.\n\nPaginated with `limit` and `cursor`: newest first, ties on time broken by\n`id`, devices without a registration time first. See the Pagination page."
              },
              "response": []
            },
            {
              "name": "Recent stored telemetry readings for the key's organisation [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/devtools/partner/v1/telemetry/latest",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "devtools",
                    "partner",
                    "v1",
                    "telemetry",
                    "latest"
                  ],
                  "query": [
                    {
                      "key": "asset_id",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "hours",
                      "value": "24",
                      "description": "The window, in hours before the first page's time. A filter. Raising `hours` to read further back was the way to page before `cursor`; that still works but is deprecated as a way to page. Use `cursor`. Range 1 to 168.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "100",
                      "description": "Page size. Default 100, at most 500. Range 1 to 500.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Opaque. The `next_cursor` of the previous page, sent unchanged; omit it for the first page. Send the same filters with every page. A cursor that cannot be read, that belongs to another operation, or that comes with different filters is `400` `invalid_cursor`. A cursor names no organisation and grants nothing: every page is read for your own organisation. See the Pagination page.",
                      "disabled": true
                    }
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key with scope `telemetry:read`.\n**Reference:** https://www.ampverve.com/api-docs/#op-partnerlatesttelemetry\n\nScope `telemetry:read`. Readings stored in the last `hours` (default 24, at most\n168), newest first, optionally for one `asset_id`. An asset of another\norganisation returns an empty list, not an error. A sandbox key gets generated\nfive minute readings for the simulated assets.\n\nPaginated with `limit` and `cursor`: newest first, ties on time broken by the\norder the readings were stored. The window is fixed by the first page: later\npages read the same `hours` before the first page's time, so it does not\nslide while you page. Send the same `asset_id` and `hours` with every page.\nSee the Pagination page."
              },
              "response": []
            }
          ],
          "description": "Read operations authenticated by an organisation API key (`Authorization: Bearer avk_...`),\nscoped to the key's organisation and to the key's scopes. A sandbox key gets\nsimulated data from the same operations; see `Sandbox`.\n\n**Rate limit.** Each key has a per minute limit (600 by default), counted\nacross every server, and reported in the `RateLimit-*` headers; over it, `429`\nwith `Retry-After`. If the shared counter store is briefly unreachable, reads\nare still answered and limited per server, and every operation that writes\nwith an API key (the sandbox test event and the Home Assistant connector\npushes) answers `503` with `Retry-After: 30` and error code\n`service_unavailable` rather than accept a write without its limit. Retry a\n`503` after `Retry-After`; it is never a sign the key is wrong."
        },
        {
          "name": "Sandbox",
          "item": [
            {
              "name": "What the sandbox simulates [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/devtools/partner/v1/sandbox/scenario",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "devtools",
                    "partner",
                    "v1",
                    "sandbox",
                    "scenario"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key, no scope needed.\n**Data basis:** simulated. Method: Deterministic sandbox simulator: two simulated sites, their devices and five minute telemetry. Nothing is measured or stored. Not a measurement: do not use it for settlement or a financial claim unless the operation says so.\n**Reference:** https://www.ampverve.com/api-docs/#op-sandboxscenario\n\nSandbox keys only; a live key gets 403 `sandbox_only`. No scope needed. The\nsimulated sites, the device and asset identifiers at each, how each asset's\ntelemetry is generated, the alert identifiers and the event types the sandbox\ncan send. Use it to write assertions in an integration test."
              },
              "response": []
            },
            {
              "name": "Send a signed sample event to the sandbox's webhook endpoints [live]",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Idempotency-Key",
                    "value": "3f6c1d2e-8a4b-4c5d-9e7f-0a1b2c3d4e5f",
                    "description": "1 to 255 visible ASCII characters, unique per intended request, such as a UUID. Optional.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/devtools/partner/v1/sandbox/webhooks/test",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "devtools",
                    "partner",
                    "v1",
                    "sandbox",
                    "webhooks",
                    "test"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** API key, no scope needed.\n**Data basis:** simulated. Method: A signed sample event generated by the sandbox simulator. Not a measurement: do not use it for settlement or a financial claim unless the operation says so.\n**Reference:** https://www.ampverve.com/api-docs/#op-sandboxsendtestevent\n\nSandbox keys only; a live key gets 403 `sandbox_only`. No scope needed. Queues one\nevent of `event_type` (default `webhook.test`) for every active sandbox endpoint\nsubscribed to it, or for `endpoint_id` only. The event is a generated sample in\nthe shape of the live event with `data.sandbox` set to `true`, signed with the\nendpoint's secret, and delivered and retried exactly as a live event. Register a\nsandbox endpoint first with `POST /api/devtools/webhooks?environment=sandbox`.\n\n**Idempotency.** Send an `Idempotency-Key` header to make a retry safe. A repeat\nof the same request (same `event_type` and `endpoint_id`) with the same key, in\nthe same organisation, queues nothing and returns the first `202` body again with\n`Idempotent-Replayed: true`. The same key with a different request is `409`. Only\na request that queued an event is remembered, so a retry after a `4xx` or `5xx`\nis a fresh attempt. Keys are scoped to the organisation, not to the API key, and\nare remembered as long as the event is kept; AmpVerve stores a hash of the key,\nnever the key. This is the only operation that writes with an API key, and so\nthe only one that accepts the header.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"event_type\": \"device.offline\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ],
          "description": "A demo environment organisation of simulated data for building and testing an\nintegration without touching any real home, site, device or market.\n\n1. An organisation administrator issues a sandbox key with\n   `POST /api/devtools/apikeys?environment=sandbox`, and registers a sandbox\n   webhook endpoint with `POST /api/devtools/webhooks?environment=sandbox`. Every\n   key and webhook operation takes `environment=sandbox`.\n2. The key is an ordinary `avk_` key. Its organisation is the caller's sandbox\n   organisation, `demo-sandbox-<16 hex>`, derived from the caller's own\n   organisation; `GET /api/devtools/partner/v1/me` returns it with\n   `environment: sandbox`.\n3. Every Partner API read answers a sandbox key from a deterministic simulator:\n   a depot with two chargers and a solar and battery site, their devices,\n   five minute telemetry for six assets and two alerts. The same request at the\n   same moment gives the same answer. Nothing is measured and nothing is stored.\n4. `POST /api/devtools/partner/v1/sandbox/webhooks/test` sends a signed sample\n   event to the sandbox's endpoints, delivered and retried exactly as a live one.\n\n**Guarantees.** A sandbox key never reaches a non-demo organisation: its\norganisation is fixed on the key row and no header, parameter or body changes it.\nIt never reaches a real device or a market write: the Partner API operations\nanswer it without reading any stored device, alert or telemetry row; a sandbox\nkey holds read scopes only (`alerts:read`, `devices:read`, `telemetry:read`), so\nthe only other operations that accept an API key, the Home Assistant connector\nwrites, refuse it for lacking `telemetry:write`; every other operation refuses\nan API key with 401; and the only Partner API operation that writes is the\nsandbox test event. Expiry, revocation and the rate limit apply as for a live\nkey."
        }
      ]
    },
    {
      "name": "Platform and access",
      "item": [
        {
          "name": "Authentication",
          "item": [
            {
              "name": "Exchange credentials for an access token [live]",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/auth/login",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "auth",
                    "login"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-login\n\nReturns a 60 minute access token and a 7 day refresh token.\n\nThe access token is a JWT carrying your `tenant_id` (your organisation id),\nyour `roles`, and `exp`. Every subsequent request must send it as\n`Authorization: Bearer <access_token>`.\n\nRate limited to 5 requests per minute per source IP, shared across all\nauthentication endpoints. This is deliberately tight. Do not call it per\nrequest. Cache the access token and refresh it on expiry.\n\nAfter 5 consecutive failed attempts the account locks for 15 minutes and\nreturns `423`.",
                "auth": {
                  "type": "noauth"
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"email\": \"integration@example.com\",\n  \"password\": \"<your password>\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Rotate an access token [live]",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/auth/token/refresh",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "auth",
                    "token",
                    "refresh"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-refreshtoken\n\nExchanges a refresh token for a new access token and a **new** refresh token.\nThe refresh token you present is revoked in the same operation. Refresh tokens\nare single use. Store the new one.\n\nA refresh token is only valid while its `jti` is present in the AmpVerve token\nallowlist. Calling `/api/auth/logout` removes it immediately.",
                "auth": {
                  "type": "noauth"
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"refresh_token\": \"\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Exchange a Microsoft Entra ID token for an AmpVerve session [live]",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/auth/sso/entra/exchange",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "auth",
                    "sso",
                    "entra",
                    "exchange"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-entrassoexchange\n\nSingle sign-on (row G8). The browser signs the user in with Entra (MSAL)\nand presents the ID token here. The token becomes a session only when:\nits `tid` matches an enabled provider row an organisation administrator\nconfigured (`PUT /api/auth/sso/entra/provider`); its signature verifies\nagainst that authority's published keys; issuer, audience (the\nconfigured application id) and validity check out; and at least one of\nits `groups` maps to a role in the organisation's group to role\nmappings. The roles on the session are the union of the mapped roles.\n\nRefusals carry `detail.error: sso_refused` and a plain `detail.reason`:\n`404` when no organisation is configured for the token's tenant (the\nconsole then keeps its existing Microsoft path), `401` for a token that\ndoes not verify, `403` when no group maps to a role (the group ids are\nlisted) or the token signals groups overage, which is refused rather\nthan completed from Graph. Password login is a separate route and is\nunchanged. The sign in is recorded in the audit chain as\n`auth.sso.login`.",
                "auth": {
                  "type": "noauth"
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"id_token\": \"\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ],
          "description": "Obtain and refresh the bearer token that authorises every other call, and\nMicrosoft Entra ID single sign-on."
        },
        {
          "name": "Platform",
          "item": [
            {
              "name": "Ingestion liveness [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/telemetry/healthz",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "telemetry",
                    "healthz"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-getingestionliveness\n\nReturns 200 while the ingestion service process is running. It does not\ncheck dependencies. Use `/api/telemetry/readyz` to determine whether the\ningestion path can actually accept and durably store data.",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            },
            {
              "name": "Ingestion readiness and dependency health [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/telemetry/readyz",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "telemetry",
                    "readyz"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-getingestionreadiness\n\nThe authoritative machine readable signal for whether the ingestion layer is\nhealthy. This is the endpoint a consumer should poll to detect degradation,\nand it is the endpoint AmpVerve measures the ingestion uptime commitment\nagainst.\n\nReturns **200** with `status: \"ready\"` when the ingestion path is accepting\ntraffic. Returns **503** with `status: \"degraded\"` otherwise.\n\nThe `checks` object reports each dependency independently:\n\n| Check      | Meaning if false |\n| ---------- | ---------------- |\n| `kafka`    | The streaming bus is unreachable. Writes will fail. Hard failure. |\n| `redis`    | The live event stream is unreachable. Writes will fail. Hard failure. |\n| `database` | The time series store is unreachable. **This does not set `status` to `degraded`.** Ingestion continues to accept and stream data, but `stored` in the ingest response will be `0`. This is the single most important degradation mode for a consumer to detect, because the request still returns 200. |\n\nSee the Reliability section of the published documentation for what to do\nabout each case.",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            },
            {
              "name": "Prometheus metrics for the ingestion layer [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/telemetry/metrics",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "telemetry",
                    "metrics"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Reference:** https://www.ampverve.com/api-docs/#op-getingestionmetrics\n\nPrometheus text exposition format. Includes `telemetry_ingest_total`\n(cumulative records accepted) and `telemetry_readiness` (1 ready, 0 degraded).\nUseful if you scrape AmpVerve into your own observability stack.",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ],
          "description": "Liveness, readiness, country capability and operational signals."
        },
        {
          "name": "Platform status",
          "item": [
            {
              "name": "Public API status by component (no credential) [live]",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/devtools/status",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "devtools",
                    "status"
                  ]
                },
                "description": "**Status: live** (Live). See https://www.ampverve.com/api-docs/#status-labels for what each label means.\n**Credential:** none. This operation is public.\n**Data basis:** measured.\n**Reference:** https://www.ampverve.com/api-docs/#op-getpublicapistatus\n\nPublic and read only: no credential, no organisation. For each public API\ncomponent (Partner API and webhooks, authentication, telemetry, sites and\ndevices, optimisation, tariffs and markets, notifications), the state now and\nthe observed availability over `24h`, `7d` and `30d`.\n\nEvery figure is counted from the diagnostics prober's recorded checks of each\nservice's `/healthz` inside the cluster, every `probe_interval_seconds`, and\nfrom nothing else. `state` is `operational` or `down` from the latest check,\nand `unobserved` when there is none newer than three intervals. A window with\nno checks has `observed: false` and a null `availability_pct`, never 100.\n`overall` is `operational`, `degraded` (one component down), `major_outage`\n(more than one), `partially_observed` or `unobserved`. A healthy `/healthz`\nshows a service is answering; it does not prove every operation behind it\nworks. No uptime commitment is stated unless the operator configured one.\n\nIncidents are not public: signed in organisations read them with\n`GET /api/hardware/platform/status`. The answer is cached for 60 seconds.\nRendered at https://app.ampverve.com/api-docs/status.",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ],
          "description": "Platform status, observed availability and incident history visible to your\norganisation. Opening and updating incidents is for platform administrators.\n`GET /api/devtools/status` is the public, unauthenticated summary by API\ncomponent, rendered at https://app.ampverve.com/api-docs/status."
        }
      ]
    }
  ]
}
