# Build on the network that runs your operations

Programmatic control of your Ericsson NetCloud Manager fleet — routers, subscriptions, private cellular, and real-time events. REST APIs, webhooks, and an LLM-ready reference.

[Get Started](#quickstart)

**50+**
API Endpoints

**v2 & v3**
API Versions

**REST**
Architecture

**Webhooks**
Real-time Events

## Make your first API call in 3 steps

No external code required. Everything you need is right here.

**1**

### Get Your API Credentials
Log into [NetCloud Manager](https://accounts.cradlepointecm.com), navigate to **Tools → NetCloud API**, and generate your API keys.

**v3 (Recommended):** You’ll get a Bearer token.
**v2 (Legacy):** You’ll get CP API-ID/key and ECM API-ID/key pairs.

**2**

### Make Your First Request

**cURL**

```bash
# List your subscriptions using API v3
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://api.cradlepointecm.com/api/v3/subscriptions"
```

**Python**

```python
import requests

resp = requests.get(
    "https://api.cradlepointecm.com/api/v3/subscriptions",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
)
print(resp.json())
```

**JavaScript**

```javascript
const resp = await fetch("https://api.cradlepointecm.com/api/v3/subscriptions", {
  headers: { Authorization: "Bearer YOUR_TOKEN" },
});
const data = await resp.json();
```

**3**

### See the Response

```json
{
  "data": [
    {
      "id": "112345",
      "name": "NetCloud Branch Essentials",
      "start_time": "2026-01-15T00:00:00Z",
      "end_time": "2027-01-15T00:00:00Z",
      "quantity": 25,
      "feature_list_id": "nc-branch-essentials"
    }
  ],
  "links": { "next": "/api/v3/subscriptions?page[after]=abc123" }
}
```

That’s it. You’re in. Now explore the full API below.

**Common gotchas:**

- **401 Unauthorized** — the auth scheme must match the endpoint version: Bearer tokens work only on `/api/v3/` paths, API-key headers only on `/api/v2/` paths. Expired or revoked credentials also 401 — regenerate under **Tools → NetCloud API**.
- **Pagination** — v3 is cursor-based: follow the next-page URL in the response (`page[after]` cursor) instead of building offsets. v2 uses `?limit=N&offset=M` and returns `meta.next`.
- **429 Too Many Requests** — you hit the rate limit. If the response carries an `X-RateLimit-Reset` header (a Unix epoch), wait until then; otherwise back off exponentially (1s, 2s, 4s, 8s) and give up after 5 attempts.

## Two API layers

Device-level control and fleet orchestration. Use v3 for new integrations.

### Users

Create, update, and manage user accounts and permissions.

### Subscriptions

View and manage device subscriptions and licensing.

### Private Cellular

Configure and monitor private cellular networks, cores, SIMs, and radios.

### Exchange Resources

Create and manage NetCloud Exchange resources and sites.

### Exchange Sites

Create and manage NetCloud Exchange site configurations.

**Base URL**
`https://api.cradlepointecm.com/api/v3/`

[View Complete v3 API Reference →](/developer/api/)

### Routers

Legacy device inventory and status for the v2 fleet API.

### Accounts

Manage NetCloud accounts and sub-account hierarchies.

### Groups

Organize routers into configuration and policy groups.

### Alert Rules

Configure which events fire notifications and webhooks.

### Signal Samples

Historical cellular signal metrics per device.

### Push Destinations

Register HTTP destinations for webhook delivery.

**Base URL**
`https://www.cradlepointecm.com/api/v2/`

[View Complete v2 API Reference →](/developer/api/)

## v2 vs v3 at a glance

What changes between the layers, at a glance. Note that the version split is by capability, not preference: most fleet and device endpoints — routers, alerts, device health and telemetry — are **v2 today**, while Private Cellular (5G), eSIM, and NetCloud Exchange are **v3**. Each endpoint in the API reference states its own version and base URL.

|  | API v2 | API v3 |
| --- | --- | --- |
| Authentication and authorization | Cradlepoint API Keys and NetCloud Manager API Keys (application identification) | Single Bearer token (user identification) |
| Documentation specification | Swagger 1.2 | OpenAPI 3.0 |
| Pagination | Offset | Cursor |
| Expandable fields | Yes | No |
| URL | https://www.cradlepointecm.com/api/v2/ — include the trailing slash; without it the call redirects and two calls are attributed to your account | https://api.cradlepointecm.com/api/v3 — no trailing slash on v3 endpoints |
| Content type | application/json | application/vnd.api+json |

## Your language. Your workflow.

Official libraries and tools to accelerate your integration.

### Python SDK

The ncm package — full-featured Python client for NCM APIs. Install with: pip install ncm

### Postman Collection

Pre-configured requests for every endpoint. Import and start testing.

### Script Manager

Web-based tool for bulk operations — configure devices, manage users, update subscriptions.

### Sample Scripts

Ready-to-run Python scripts for common tasks — router status, config backup, alert management.

## Real-time events. No polling.

Subscribe to network events and react instantly when things change.

[Read the full Webhooks guide →](/developer/webhooks/)

**Device Online/Offline**
Know immediately when a router connects or drops.

```json
// Device goes offline (connection_state)
{
  "data": [
    {
      "type": "connection_state",
      "friendly_info": "The device entered the \"offline\" state.",
      "info": { "state": "offline" },
      "router": 4716965,
      "router_details": { "name": "OpenWRT-99b" },
      "detected_at": "2025-03-13T05:20:18.322301+00:00"
    }
  ],
  "meta": { "record_type": "alert", "record_version": "1.0.0" }
}
```

**Configuration Changes**
Detect config drift and unauthorized modifications.

```json
// Configuration change
{
  "event": "router.config_push",
  "timestamp": "2026-04-11T14:31:12Z",
  "data": {
    "router_id": "112345",
    "name": "Store-42-Primary",
    "change": "config_push",
    "status": "applied",
    "actor": "automation@example.com"
  }
}
```

**Alert Triggers**
Threshold breaches, connectivity issues, security events.

```json
// Alert triggered
{
  "event": "alert.triggered",
  "timestamp": "2026-04-11T14:32:05Z",
  "data": {
    "router_id": "112345",
    "alert_type": "signal_strength_alert",
    "threshold": -90,
    "value": -97,
    "severity": "warning"
  }
}
```

**Firmware Updates**
Track update progress across your fleet in real time.

```json
// Firmware update
{
  "event": "router.firmware_update",
  "timestamp": "2026-04-11T14:35:40Z",
  "data": {
    "router_id": "112345",
    "from_version": "7.22.12",
    "to_version": "7.23.10",
    "status": "in_progress",
    "progress": 0.62
  }
}
```

Webhooks power event-driven automation, AIOps, and live dashboards across your fleet.

## What will you build?

Six starters, each with a prompt you can paste straight into Claude, Cursor, or any coding agent. Every one runs against real NCM endpoints — no mock data, no scaffolding. Pick one, paste it, and you'll have something working against your own fleet in an afternoon.

**15 minutes · the "hello world" of fleet automation**

### Who's Offline Right Now?
A one-command script that prints every router that's currently offline, with how long it's been down.

```markdown
Read https://cradlepoint.ericsson.com/llms.txt and the OpenAPI document at
https://cradlepoint.ericsson.com/developer/api/openapi.json so you know the
Ericsson NetCloud Manager (NCM) API.

Build me a single-file Python script (standard library only) that prints every
router in my account that is currently offline, sorted by how long it has been
down, longest first.

Requirements:
- Call GET https://www.cradlepointecm.com/api/v2/routers/ with state=offline and
  fields=id,name,state_updated_at, so the response only carries what you need.
  Keep the trailing slash: without it the API answers 301 and costs an extra
  call.
- v2 authenticates with four headers: X-CP-API-ID, X-CP-API-KEY, X-ECM-API-ID,
  X-ECM-API-KEY. Read them from the environment variables X_CP_API_ID,
  X_CP_API_KEY, X_ECM_API_ID and X_ECM_API_KEY. Never hardcode them, and exit
  with a clear message naming any that are missing. Also send a descriptive
  User-Agent header (for example ncm-offline-routers/1.0).
- Page with limit=500 and follow meta.next exactly as returned until it is null.
  It is an absolute URL and may point at a regional host such as
  www.us0.cradlepointecm.com. Send the same headers there, but only ever send
  credentials to https:// URLs on cradlepointecm.com or its subdomains.
- Don't follow redirects automatically; treat any 3xx response as an error (a
  301 usually means a missing trailing slash). HTTP clients re-send custom
  headers to wherever a redirect points, which would leak your keys.
- Print a clean table: router name, ID, and time since state_updated_at
  (for example "3d 4h 12m"). Put routers with no timestamp last.
- On HTTP 429, wait until the X-RateLimit-Reset header (a Unix epoch) if it is
  present; otherwise back off exponentially (1s, 2s, 4s, 8s). Make at most
  5 attempts per request in total, and never wait more than 60s at a time.
  On any other HTTP error, stop with a clear message. Use a 30s request timeout.

Then tell me exactly how to run it.
```

APIs used: `GET /api/v2/routers/`

**30 minutes · your first automation**

### Morning Fleet Digest
A scheduled job that posts a short daily summary to Slack: how many routers are in each state, what alerted in the last 24 hours, and which routers changed state.

```markdown
Read https://cradlepoint.ericsson.com/llms.txt and the OpenAPI document at
https://cradlepoint.ericsson.com/developer/api/openapi.json so you know the
Ericsson NetCloud Manager (NCM) API.

Build me a Python script (standard library only) that produces a once-a-day
summary of my NCM fleet and posts it to a Slack incoming webhook.

The digest should contain:
- Total routers and the count in each state. Pull GET /api/v2/routers/ once
  with fields=id,name,state,state_updated_at and group the results client-side.
- Alerts from the last 24 hours, from GET /api/v2/alerts/ with
  created_at__gt=<ISO 8601 UTC timestamp, e.g. 2026-10-25T07:00:00Z>. Group
  them by `type`, sorted by count. On each type's line, show its count and up
  to 3 distinct `friendly_info` strings inline, each cut to 60 characters.
  (Don't send an epoch number; the API rejects it with 409.)
- Routers whose state changed in the last 24 hours: GET /api/v2/routers/ with
  state_updated_at__gt=<same ISO timestamp> and the same fields. List them
  newest first.

Requirements:
- Keep the Slack message under 20 lines: a bulleted summary, not JSON. Show at
  most the top 5 alert types and the 5 most recent state changes. Summarise
  the rest in one "+N more" line each; for alerts, give both the number of
  extra types and the number of extra alerts. If there were no alerts, say so
  in one line.
- Add a --dry-run flag that prints the message instead of posting it.
- v2 paths keep the trailing slash. Auth is four headers (X-CP-API-ID,
  X-CP-API-KEY, X-ECM-API-ID, X-ECM-API-KEY) read from the environment
  variables X_CP_API_ID, X_CP_API_KEY, X_ECM_API_ID and X_ECM_API_KEY. Read the
  webhook URL from SLACK_WEBHOOK_URL. Never hardcode any of them. Send a
  descriptive User-Agent header.
- Page every list call with limit=500 by following meta.next exactly as
  returned until it is null. Don't compute offsets yourself; alerts may page
  with a cursor rather than an offset. meta.next may point at a regional host
  such as www.us0.cradlepointecm.com. Send the same headers there, but only
  ever send credentials to https:// URLs on cradlepointecm.com or its
  subdomains.
- Don't follow redirects automatically; treat any 3xx response as an error (a
  301 usually means a missing trailing slash). HTTP clients re-send custom
  headers to wherever a redirect points, which would leak your keys.
- On HTTP 429, wait until X-RateLimit-Reset (a Unix epoch) if that header is
  present, otherwise back off exponentially (1s, 2s, 4s, 8s). Make at most
  5 attempts per request in total, and never wait more than 60s at a time.
  On any other HTTP error, stop with a clear message. Use a 30s request timeout.

Then give me a ready-to-use crontab line for 7am. Note that cron uses the
machine's local time zone, and that cron doesn't inherit my shell environment,
so source the variables from a file only I can read (chmod 600).
```

APIs used: `GET /api/v2/routers/`, `GET /api/v2/alerts/`

**30 minutes · the report everyone asks you for**

### Fleet Inventory Export
Dump every router to a CSV — name, model, firmware, group, state and location — so you can hand auditors a spreadsheet instead of screenshots.

```markdown
Read https://cradlepoint.ericsson.com/llms.txt and the OpenAPI document at
https://cradlepoint.ericsson.com/developer/api/openapi.json so you know the
Ericsson NetCloud Manager (NCM) API.

Build me a Python script (standard library only) that exports my full NCM
router inventory to a CSV file whose path I pass as an argument.

Use exactly these CSV columns, in this order:
name, id, mac, serial_number, model, firmware_version, group, state, latitude,
longitude, site_info
(group is the group's name, taken from the expanded group.)

Requirements:
- Start from GET https://www.cradlepointecm.com/api/v2/routers/ with
  expand=group,last_known_location. Those two, plus account, are the only
  relations /routers/ can expand. The product and firmware relations are NOT
  expandable; expand=product or expand=actual_firmware returns HTTP 400.
- For model and firmware, collect the distinct product and actual_firmware IDs
  from the router URLs. Then fetch each set once with
  GET /api/v2/products/?id__in=<comma-separated IDs>&limit=500 and
  GET /api/v2/firmwares/?id__in=<comma-separated IDs>&limit=500, in chunks of
  100 IDs, following meta.next on those too. Join the results client-side.
  Don't make one request per router. A failed product or firmware lookup is
  not fatal: print a warning and use the fallbacks below. Only errors on the
  /routers/ calls stop the script.
  - model = the product's name. If the product lookup fails, use the router's
    full_product_name, unless it is "unknown".
  - firmware_version = the firmware's version. Leave it empty if
    actual_firmware is null (common for routers that have never connected) or
    if the firmware lookup fails.
- NCM stores coordinates, not street addresses. Take latitude/longitude from
  the expanded last_known_location, which is null when a router has never
  reported a location. For site_info, join the router's description, custom1
  and custom2 fields with " | ", skipping empty ones. That's where teams
  usually record a site address.
- v2 paths keep the trailing slash. Auth is four headers (X-CP-API-ID,
  X-CP-API-KEY, X-ECM-API-ID, X-ECM-API-KEY) read from the environment
  variables X_CP_API_ID, X_CP_API_KEY, X_ECM_API_ID and X_ECM_API_KEY. Send a
  descriptive User-Agent header.
- Page with limit=500 by following meta.next exactly as returned until it is
  null. It may point at a regional host such as www.us0.cradlepointecm.com.
  Send the same headers there, but only ever send credentials to https:// URLs
  on cradlepointecm.com or its subdomains.
- Don't follow redirects automatically; treat any 3xx response as an error (a
  301 usually means a missing trailing slash). HTTP clients re-send custom
  headers to wherever a redirect points, which would leak your keys.
- On HTTP 429, wait until X-RateLimit-Reset (a Unix epoch) if that header is
  present, otherwise back off exponentially (1s, 2s, 4s, 8s). Make at most
  5 attempts per request in total, and never wait more than 60s at a time.
  On any other HTTP error, stop with a clear message. Use a 30s request timeout.
- Write the CSV with a header row. Leave cells empty, rather than writing
  "None" or "null", when data is missing, so the file opens cleanly in Excel.

Then tell me how to run it, including which environment variables to set.
```

APIs used: `GET /api/v2/routers/` (`expand=group,last_known_location`), `GET /api/v2/products/`, `GET /api/v2/firmwares/`

**1 hour · your first real analysis**

### Weak Signal Finder
Rank your routers by worst cellular signal over the last week, so you know exactly where to send a better antenna.

```markdown
Read https://cradlepoint.ericsson.com/llms.txt and the OpenAPI document at
https://cradlepoint.ericsson.com/developer/api/openapi.json so you know the
Ericsson NetCloud Manager (NCM) API.

Build me a Python script (standard library only) that finds the routers with
the worst cellular signal over the past 7 days. It covers every router that
has at least one cellular modem.

Approach:
- List cellular modems with
  GET /api/v2/net_devices/?mode=wan&is_asset=true&expand=router&limit=500.
  `mode` only accepts wan or lan (mode=cellular silently returns nothing);
  is_asset=true is what selects modems. expand=router puts the router (id and
  name) inline. Skip modems whose router is null; they aren't attached to a
  router.
- Pull samples from GET /api/v2/net_device_signal_samples/ with
  net_device__in=<comma-separated modem IDs>,
  created_at__gt=<UTC timestamp 7 days ago, ending in Z, e.g.
  2026-10-19T17:00:00Z>, limit=500 and fields=net_device,created_at,rsrp,sinr.
  The API reference caps a single query at 100 devices, so batch the modem IDs
  in groups of 100.
- Paging: this endpoint has no offset. Follow meta.next exactly as returned
  until it is null. If a next URL is missing any of your parameters
  (net_device__in, created_at__gt, fields, limit), add them back before
  requesting it. If you set order_by, use only created_at_timeuuid or
  -created_at_timeuuid; ordering by created_at or by a metric makes the API
  return an error.
- Each sample's net_device is a URL; take the modem ID from its last path
  segment. Pool the samples of all a router's modems together.
- For each router, compute the sample count, plus average and 5th-percentile
  RSRP and SINR, using linear interpolation for the percentile. Skip null
  readings in the maths, but report how many there were. Rank by
  5th-percentile RSRP, lowest first; routers whose RSRP readings are all null
  go last. Print the 20 worst as a table.

Requirements:
- List routers that reported zero samples separately, below the table, so
  "no data" is never mistaken for "good signal".
- v2 paths keep the trailing slash. Auth is four headers (X-CP-API-ID,
  X-CP-API-KEY, X-ECM-API-ID, X-ECM-API-KEY) read from the environment
  variables X_CP_API_ID, X_CP_API_KEY, X_ECM_API_ID and X_ECM_API_KEY. Send a
  descriptive User-Agent header.
- meta.next may point at a regional host such as www.us0.cradlepointecm.com.
  Send the same headers there, but only ever send credentials to https:// URLs
  on cradlepointecm.com or its subdomains.
- Don't follow redirects automatically; treat any 3xx response as an error (a
  301 usually means a missing trailing slash). HTTP clients re-send custom
  headers to wherever a redirect points, which would leak your keys.
- This is the starter project most likely to hit the rate limit on a large
  fleet. On HTTP 429, wait until X-RateLimit-Reset (a Unix epoch) if that
  header is present, otherwise back off exponentially (1s, 2s, 4s, 8s). Make at
  most 5 attempts per request in total, and never wait more than 60s at a time.
  On any other HTTP error, stop with a clear message. Use a 30s request timeout.

Then tell me how to run it, including which environment variables to set.
```

APIs used: `GET /api/v2/net_devices/`, `GET /api/v2/net_device_signal_samples/`

**1 hour · a change history for every router**

### Config Change Tracker
Snapshot the configuration NCM manages for each router into git every night, so you get a readable diff whenever someone changes it in NCM, and a list of routers whose changes haven't reached the device yet.

```markdown
Read https://cradlepoint.ericsson.com/llms.txt and the OpenAPI document at
https://cradlepoint.ericsson.com/developer/api/openapi.json so you know the
Ericsson NetCloud Manager (NCM) API.

Build me a Python script (standard library plus the git CLI) that snapshots the
NCM-managed configuration of every router in my account into a local git
repository, one JSON file per router, and commits only when something changed,
so I can `git log -p` config changes over time. Take the repository path as a
command-line argument, and create the repository if it doesn't exist.

Background you need:
- GET /api/v2/configuration_managers/ returns one record per router. Its
  `target` field is the configuration NCM manages for that router, as a
  two-element list [updates, removals]. These are the settings changed from
  the device defaults through NCM, not the router's full running config. A
  router with nothing managed shows [{}, []]. For managed routers, target
  includes ecm.config_version, a counter that goes up with every NCM change.
- Changes someone makes locally on the device do not appear here. The API
  reference describes this endpoint as where "config drift is detected and
  corrected", but don't describe this script as detecting local drift.
- `synched` is false when NCM's target hasn't been applied to the device yet.
  `suspended` is true when config sync is paused for that router.

Requirements:
- Fetch everything in one paged pass with
  GET /api/v2/configuration_managers/?expand=router&limit=500, so each record
  carries its router's name and ID. Don't make one call per router. Finish
  fetching every page before writing anything. If any request fails, or the
  API returns no records at all, exit non-zero without touching the
  repository. Skip any record whose router is null, with a warning.
- Snapshot the `target` field. Write stable output: sorted keys, 2-space
  indent, trailing newline. Name each file <router-id>.json, so renaming a
  router doesn't break its history. Also write INDEX.md, a table of router ID,
  name and file, sorted by name. Delete files for routers that no longer exist.
- Redact secrets before anything touches disk:
  - A key is sensitive if its name contains password, passphrase, psk,
    secret, token or community, or is exactly "key" or ends in "_key"
    (case-insensitive).
  - Under a sensitive key, replace every string and number value with
    "<redacted>", including values nested in dicts and lists below it. Keep
    the structure, and leave null and booleans as they are, so diffs still show
    a setting being cleared or toggled.
  - In any string, replace each PEM block (-----BEGIN ... -----END ...-----)
    with "<redacted>".
  - Never drop a key, so redaction never looks like a deleted setting in the
    diff.
- After the snapshot, print routers where synched is false ("NCM changes not
  yet applied") and where suspended is true ("sync paused").
- Stage only the files this script writes. Commit only if they changed; never
  create empty commits. Commit as
  "NCM Snapshot <ncm-snapshot@localhost>", so it works under cron without git
  config.
- v2 paths keep the trailing slash. Auth is four headers (X-CP-API-ID,
  X-CP-API-KEY, X-ECM-API-ID, X-ECM-API-KEY) read from the environment
  variables X_CP_API_ID, X_CP_API_KEY, X_ECM_API_ID and X_ECM_API_KEY. Send a
  descriptive User-Agent header.
- Follow meta.next exactly as returned until it is null. It may point at a
  regional host such as www.us0.cradlepointecm.com. Send the same headers
  there, but only ever send credentials to https:// URLs on cradlepointecm.com
  or its subdomains.
- Don't follow redirects automatically; treat any 3xx response as an error (a
  301 usually means a missing trailing slash). HTTP clients re-send custom
  headers to wherever a redirect points, which would leak your keys.
- On HTTP 429, wait until X-RateLimit-Reset (a Unix epoch) if that header is
  present, otherwise back off exponentially (1s, 2s, 4s, 8s). Make at most
  5 attempts per request in total, and never wait more than 60s at a time.
  On any other HTTP error, stop with a clear message. Use a 30s request timeout.

This script must be strictly read-only against NCM: GET requests only. Never
PUT, PATCH, POST or DELETE.

Then give me a crontab line to run it nightly. Cron doesn't inherit my shell
environment, so source the variables from a file only I can read (chmod 600).
```

APIs used: `GET /api/v2/configuration_managers/` (`expand=router`, `synched`, `suspended`)

**1 hour · point an AI agent at your fleet**

### Ask Your Network in Plain English
Wire NCM into Claude Code or Cursor as a read-only MCP server, then ask "which stores are offline?" or "which routers had the weakest signal this week?" in plain language.

**Before you start:** Python 3.10 or newer (the `mcp` SDK requires it; macOS's built-in Python is 3.9).

```markdown
Read https://cradlepoint.ericsson.com/llms.txt and the OpenAPI document at
https://cradlepoint.ericsson.com/developer/api/openapi.json so you know the
Ericsson NetCloud Manager (NCM) API. For project layout, you can look at the
reference servers in https://github.com/cradlepoint/api-samples under
ncm_mcp_servers. Those include write tools (router management, config
patching, reboot); do NOT copy any of them.

Build me a read-only MCP server in Python 3.10+ using the official `mcp` SDK
with the stdio transport. Pin the `mcp` version you install and use the server
API documented for that version, because module paths changed between 1.x and
2.x.

Expose these tools:
- list_routers(state?, group_name?, name?) -> GET /api/v2/routers/. state is
  online, offline or initialized. The API filters groups by ID, so resolve
  group_name with GET /api/v2/groups/?name=<name> first (exact,
  case-sensitive match). Say so clearly if no group matches; if several do,
  include them all with group__in.
- recent_alerts(hours=24, type?) -> GET /api/v2/alerts/ with
  created_at__gt=<ISO 8601 UTC timestamp>.
- wan_interfaces(router) -> GET /api/v2/net_devices/?router=<id>&mode=wan
- signal_summary(router, days=7) -> list the router's modems with
  GET /api/v2/net_devices/?router=<id>&is_asset=true, then page through
  GET /api/v2/net_device_signal_samples/?net_device__in=<ids>&created_at__gt=<ts>
  for the whole window. Return per modem: the sample count, plus average and
  minimum rsrp and sinr. Report the 5G fields (rsrp5g, sinr5g and their
  _highband variants) the same way, as separate figures computed over the
  samples where they are non-null; omit any that are null in every sample.
- subscriptions() -> GET /api/v3/subscriptions

For any `router` argument: if it is all digits, treat it as a router ID and
confirm it exists with GET /api/v2/routers/<id>/. Otherwise look it up with
GET /api/v2/routers/?name=<value>, which is an exact, case-sensitive match.
Return a clear error if no router matches or several do.

Requirements:
- Read-only. GET requests only. Don't implement any POST, PUT, PATCH or DELETE,
  and don't expose a generic "call any endpoint" tool.
- The two API versions authenticate differently, and mixing them up is the
  most common cause of 401s:
  - v2: base https://www.cradlepointecm.com/api/v2/, with a trailing slash on
    every path. Four headers, X-CP-API-ID, X-CP-API-KEY, X-ECM-API-ID and
    X-ECM-API-KEY, from the environment variables X_CP_API_ID, X_CP_API_KEY,
    X_ECM_API_ID and X_ECM_API_KEY.
  - v3: base https://api.cradlepointecm.com/api/v3/, with no trailing slash.
    The header Authorization: Bearer <token>, from the environment variable
    NCM_API_TOKEN.
  - Send a descriptive User-Agent header on both.
- Return compact JSON. On v2 list calls, pass fields=<comma-separated names>,
  so the API returns only the few fields needed to answer a question. `fields`
  works on the live v2 API but isn't in the OpenAPI document yet.
- Handle paging inside each tool:
  - On v2, follow meta.next exactly as returned. It may point at a regional
    host such as www.us0.cradlepointecm.com. Send the same headers there, but
    only ever send credentials to https:// URLs on cradlepointecm.com or its
    subdomains.
  - Don't follow redirects automatically; treat any 3xx response as an error
    (a 301 usually means a missing trailing slash). HTTP clients re-send
    custom headers to wherever a redirect points, which would leak your keys.
  - On v3, follow links.next (cursor page[after]).
  - list_routers, recent_alerts, wan_interfaces and subscriptions return at
    most 200 items each, and say so in the response when you truncate, so the
    model doesn't report a partial list as complete. signal_summary aggregates
    every sample in the window instead of capping them.
- On HTTP 429, wait until X-RateLimit-Reset (a Unix epoch) if that header is
  present, otherwise back off exponentially (1s, 2s, 4s, 8s). Make at most
  5 attempts per request in total, and never wait more than 60s at a time.
  Return other HTTP errors to the model as a clear error message. Use a 30s
  request timeout.
- Write clear tool descriptions and parameter docs. The assistant picks tools
  based on those.

Then show me the exact command to register this server with Claude Code
(`claude mcp add ...`), passing the credentials as environment variables.
```

APIs used: `GET /api/v2/routers/`, `GET /api/v2/groups/`, `GET /api/v2/alerts/`, `GET /api/v2/net_devices/`, `GET /api/v2/net_device_signal_samples/`, `GET /api/v3/subscriptions`

These are on-ramps. The same request patterns scale up to full NOC dashboards, ITSM and ServiceNow integrations, multi-tenant MSP portals, and config-as-code pipelines.

## Secure by default

TLS 1.2+, scoped tokens, and rate limiting on every request.

#### v3 — Bearer Token
Recommended for all new integrations. Single token, simple header.

```http
Authorization: Bearer <your_token>
```

#### v2 — API Key Pairs
Legacy authentication using two key pairs in custom headers.

```http
X-CP-API-ID: <cp_api_id>
X-CP-API-KEY: <cp_api_key>
X-ECM-API-ID: <ecm_api_id>
X-ECM-API-KEY: <ecm_api_key>
Content-Type: application/json
```

#### Rate limiting
All API requests are rate limited to maintain platform health. Normal responses don't include rate-limit headers, so don't build on them. When you get a `429`, wait until `X-RateLimit-Reset` (a Unix epoch) if the response carries it; otherwise back off exponentially and retry, up to 5 attempts.

## Everything else you need

### Full API Documentation

Complete endpoint reference with parameters, responses, and examples.

### GitHub Repository

SDK source, sample scripts, Postman collections, and community contributions.

### Changelog & Deprecations

Stay current with API changes, deprecation timelines, and migration guides.

### OpenAPI Document

Download the merged OpenAPI 3 spec — import into Postman, generate clients, or build your own tooling.

### Documentation Portal

Home for all technical documentation for NetCloud Manager and Wireless WAN.
