Why Automate IoD Bandwidth Changes
Lumen Internet on Demand (IoD) is billed hourly at whatever tier your circuit is currently ordered at. A 1 Gbps circuit sitting at its ordered tier 24/7 costs the same at 3am on Sunday as it does during peak business hours. The economic case for changing the tier on a schedule is obvious — most enterprise workloads have long off-peak windows where a 100 Mbps tier is more than sufficient.
The operational case is less obvious until you try to do it by hand. Logging into Lumen Connect every evening to drop a circuit to a lower tier, and again every morning to bring it back up, is not a workflow. It is a way to forget on a Friday and pay the 1 Gbps rate all weekend.
That is where the NaaS API comes in. Every action available to you in Lumen Connect — quoting, ordering, changing bandwidth, deprovisioning — is also exposed as a REST API. Once you wire up a scheduler against that API, the tier changes fire automatically at the times you chose.
This guide walks through what the API looks like, how a production automation script handles its edge cases, and where purpose-built tooling like Apptifi fits in if you do not want to build and maintain your own.
NaaS API Overview — What It Exposes
The Lumen NaaS API is a REST API available to enterprise customers with an active NaaS account. The key endpoint families, organized by what they do:
- • **`/location`** — qualifies an address for NaaS eligibility and returns available port speeds and products
- • **`/price`** — returns a real-time price quote for a given bandwidth tier and term at an eligible location
- • **`/orderRequest`** — submits an order (new service, bandwidth change, or deprovision)
- • **`/order`** — retrieves the status of a submitted order
- • **`/inventory`** — returns the customer's existing circuits and their current configuration
- • **`/modifyBandwidth`** — specifically submits a bandwidth tier change on an existing IoD circuit
For bandwidth automation, the two you actually touch in steady-state operations are `/modifyBandwidth` (to request a tier change) and `/order` (to poll for completion). The others are mostly relevant during onboarding, inventory sync, or new-location provisioning.
Authentication — OAuth2 Client Credentials
The NaaS API uses OAuth2 with the client credentials grant type. You get a client ID and client secret from the Lumen Connect portal after your NaaS account is provisioned. The token endpoint returns a short-lived bearer token that you attach to every subsequent API call.
A minimal token request in Python:
```python import requests
def get_access_token(client_id: str, client_secret: str) -> str: resp = requests.post( "https://api.lumen.com/oauth/token", data={ "grant_type": "client_credentials", "client_id": client_id, "client_secret": client_secret, }, headers={"Content-Type": "application/x-www-form-urlencoded"}, timeout=10, ) resp.raise_for_status() return resp.json()["access_token"] ```
Two things to note. First, the token typically expires after an hour — a long-running scheduler needs to refresh proactively before the token expires, not reactively on the first 401 response. Second, store the client secret in a secrets manager, not in the script. If your automation runs in a container or serverless function, inject the secret at runtime through the platform's secret store.
UNCERTAIN: the exact OAuth token URL above (`api.lumen.com/oauth/token`) is a representative example — verify the current URL in the Lumen Developer Center for your account. Lumen has moved the developer portal and documentation more than once, and the authoritative endpoint is whatever the current developer center surfaces for your tenant.
Submitting a Bandwidth Change
Once you have a token, submitting a bandwidth tier change is a single POST. The body specifies the circuit ID (`inventoryItemId`) and the target bandwidth value in Mbps:
```python def modify_bandwidth(token: str, circuit_id: str, target_mbps: int) -> str: resp = requests.post( "https://api.lumen.com/naas/v1/modifyBandwidth", json={ "inventoryItemId": circuit_id, "bandwidth": {"value": target_mbps, "unit": "Mbps"}, }, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json", }, timeout=15, ) resp.raise_for_status() return resp.json()["orderId"] ```
The API returns an order ID immediately. The tier change itself is not instant — it is queued for execution and typically completes in 2–5 minutes. The order ID is how you track whether it actually applied.
Polling for Completion
After submitting a change, poll the order endpoint until it reports a terminal state:
```python import time
TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELLED"}
def wait_for_order(token: str, order_id: str, timeout_s: int = 600) -> str: deadline = time.monotonic() + timeout_s while time.monotonic() < deadline: resp = requests.get( f"https://api.lumen.com/naas/v1/order/{order_id}", headers={"Authorization": f"Bearer {token}"}, timeout=10, ) resp.raise_for_status() state = resp.json().get("state", "").upper() if state in TERMINAL_STATES: return state time.sleep(15) raise TimeoutError(f"Order {order_id} did not reach a terminal state in {timeout_s}s") ```
A few production concerns to build in:
- • **Back off on failure.** If the poll call itself returns 5xx, sleep longer before retrying. A tight retry loop against a degraded API just amplifies the problem.
- • **Log every state transition.** For auditability, record every intermediate state the order passes through — not just the final outcome. Cost reconciliation months later relies on this log.
- • **Alert on FAILED and CANCELLED.** These states mean the circuit did not change. If your scheduler assumed it did, your next billing hour is going to surprise you.
Scheduling the Calls
The API handles the change. The scheduler decides when to call it. In the simplest form, that is a cron job:
```cron # Drop to 100 Mbps at 6pm every weekday 0 18 * * 1-5 /opt/iod/scripts/change_tier.py CIRCUIT-123 100
# Bring back to 1 Gbps at 7:45am every weekday 45 7 * * 1-5 /opt/iod/scripts/change_tier.py CIRCUIT-123 1000
# Keep at 100 Mbps on weekends (dropped Friday, raised Monday) ```
Cron is fine for a small number of circuits and static schedules. It stops being fine the moment you need:
- • **Calendar-driven schedules** — bandwidth events triggered by Outlook or Google Calendar entries
- • **Recurrence with exceptions** — "every weekday except US federal holidays"
- • **Visual overview** — seeing what every circuit will be doing next week without reading 40 cron lines
- • **Reconciliation** — knowing whether last night's change actually applied, and what it cost
- • **Multi-tenant isolation** — an MSP managing IoD across dozens of end customers
At that point the right answer is either a custom scheduling service built on a job queue (Temporal, Celery Beat, Sidekiq) or a purpose-built scheduler. Apptifi is the second option — it wraps the NaaS API automation described above in a calendar UI, keeps an audit trail of every order, retries failed changes automatically, and surfaces cost projections against your current schedule.
Error Modes to Plan For
Even with a well-structured script, the NaaS API has a handful of failure modes that are worth handling explicitly:
The circuit is locked. A circuit in the middle of another order cannot accept a new one. The API returns an error indicating another order is in flight. The right response is to wait for that order to complete and then submit yours, not to retry immediately.
The tier is unavailable. Some tier combinations are not valid for certain port types. A 100 Gbps change submitted against a 10 Gbps UNI port will fail. Validate tier eligibility at schedule-creation time, not at runtime.
Rate limiting. The API enforces per-customer rate limits. If you are running automation across many circuits concurrently, respect `Retry-After` headers and queue your changes rather than submitting them in parallel.
Token expiration mid-poll. A long-running poll can outlive its bearer token. Refresh proactively — keep the token's `expires_in` value and renew with a 5-minute buffer before it expires.
When to Build vs. Buy
If you are scheduling fewer than 5 circuits with a single static weekday/weekend pattern, a cron job against the modifyBandwidth endpoint is fine. You will spend a few hours building it and a few minutes per year maintaining it.
If you have 10+ circuits, multiple tenants, calendar-driven events, or a need for auditable reporting across the portfolio, the cost of building and maintaining that tooling is real — the NaaS API changes, OAuth rotations, retry logic, cost modeling, timezone handling, and operational visibility all compound. At that point the Apptifi Core plan at $50/month is usually lower than the monthly loaded cost of the engineer who is keeping the script alive, and you stop paying that engineer to maintain internal tooling that is not your product.
Either way, the underlying pattern is the same: OAuth-authenticated modifyBandwidth calls, polled to completion, against a schedule your business actually runs on. Automate that and you stop paying Lumen for bandwidth you are not using.
---
Apptifi is the visual NaaS bandwidth scheduler built for Lumen IoD and EoD. Connect your NaaS API credentials, build your schedule on a drag-and-drop calendar, and let Apptifi fire the modifyBandwidth calls at the right time, every time. [Start with the Apptifi Core plan at $50/month.](/)