Inventory & Export
Every number you own lives in your tenant inventory. List it page-by-page, export the whole set for finance reconciliation, and monitor low-stock buckets so you never run dry mid-campaign. All routes are tenant-scoped — you only ever see your own organization’s numbers.List Numbers
Export Inventory
Download your entire number inventory in a single request — ideal for reconciling the monthly DID line on your invoice against the active set, or syncing your CMDB.Query parameters
Response headers
CSV columns
Columns are emitted in this exact order (load-bearing for downstream billing tooling — new columns are only ever appended):tfv_status is the Toll-Free Verification status of the number. It only
applies to toll-free DIDs (not_submitted, pending, approved,
rejected); for every other number type the column reads N/A because
toll-free verification does not apply.
spend_30d is the trailing-30-day total spend for the number — recurring
monthly charge plus realised usage (voice + SMS + MMS) over the last 30
days — matching the Spend (30d) column on the dashboard Numbers list.
A number with no traffic shows just its recurring cost; the cell is left
blank only if the spend figure could not be computed.
created_at, assigned_at, and next_billing_at are written in UTC
ISO-8601 with a trailing Z and second precision — for example
2026-09-01T08:46:36Z. This is the format every spreadsheet (Excel,
Google Sheets, Numbers) and every standard date parser reads as the same
UTC instant, regardless of your time zone. An empty date (the number was
never assigned, has no next billing date, etc.) is an empty cell. The
on-screen Numbers table still renders these dates in your timezone; only
the file uses the UTC form, so two operators in different zones always
get byte-identical files for the same inventory.
JSON example
Export is a pure read of your local inventory table — no carrier call is made, and it never touches an outbound voice/SMS path. Any dashboard user with read access can export their own organization’s inventory.
Export from the dashboard
The same export is one click away in the dashboard — no API key needed.- Open Numbers in the dashboard.
- Click Export inventory (CSV) in the page header (next to Buy Number). A spinner shows while the file generates, then the browser downloads
phone-numbers-export-YYYY-MM-DD.csvand a toast confirms the download.
- Dates are always UTC in the file. The
created_at/assigned_at/next_billing_atcolumns are written in UTC ISO-8601 with a trailingZ(e.g.2026-09-01T08:46:36Z), so the file is identical for every operator regardless of their time zone and every spreadsheet or date parser reads the value as the same instant. The on-screen Numbers table still renders in your timezone; only the file uses UTC. The button still sends your browser’s timezone and locale for backward compatibility, but they no longer change the file’s date values. - Full inventory, not the visible slice. The export always covers the whole inventory — it does not inherit status-tab, country, or search filters you set on the list. To narrow a dashboard export, export everything and filter the CSV, or call the API with
?status=…&country_code=…&search=…. - 50,000-row cap. Exports stop at 50,000 rows. When the cap hits, the API sets
X-Truncated: trueandX-Truncated-At: 50000on the response — the CSV you receive is sorted and complete up to the cap. Narrow with the query parameters above to get under it.
The button always downloads the CSV format. If you need the JSON projection or a pre-filtered set, call
GET /numbers/export directly with the parameters above.Low-Stock Inventory Alerts
When you provision numbers from a pool (rotation, sticky-sender, campaign overflow), Orbit tracks how many active DIDs remain per(country, area-code prefix) bucket and flags any bucket below your threshold.
Query parameters
Response
The threshold defaults to 3 and can be overridden per-tenant via
organizations.settings.numbers.tn_low_stock_threshold. A daily scheduler emits the low-stock bucket payload through the number.inventory.low webhook event so your integration can auto-reprovision without polling.
The API response carries no
severity field. Severity levels exist only
in the dashboard banner (below), which derives them from
available_count relative to threshold. If you build your own alert
surface, replicate that derivation — the rule is documented in the
banner section.Low-inventory banner (dashboard)
The dashboard consumes this endpoint for you and shows the result as a banner at the top of the Numbers page — the same data, no polling script required.-
Hidden at steady state. When no bucket is below the threshold (
total_alerts: 0) the banner renders nothing — absence means healthy stock, not an error. It also stays hidden while the alerts query is in flight, so it never flashes on page load. -
One row per bucket. Each row names a
(country, area_code_prefix)bucket and shows its currentavailable_countagainst your configured threshold — for exampleUS · 415 — 2 available (threshold 3). When more buckets are low than the banner displays you get a+ N more bucketsline, mirroringtotal_alerts - alerts.lengthfrom the API. -
Two severity tiers. The whole banner takes the colour of its worst row:
- Critical (red) — the bucket’s
available_countis at or below half the threshold (available_count <= threshold / 2). At the default threshold of 3, a bucket with 0 or 1 available numbers is critical. - Warning (amber) — the bucket is below the threshold but above the half-threshold line.
available_countandthreshold; the API intentionally ships the raw counts only, so your own integration can apply the same rule (or your own). - Critical (red) — the bucket’s
- Buy more from the banner. The banner’s Buy numbers action takes you straight to the buy flow for replenishment. The purchase draws from your existing wallet balance — you are only asked to top up when the balance cannot cover the order.
Reads degrade, never 503
GET /numbers/inventory-alerts is an idempotent read with a deliberate failure policy: when the inventory store is briefly unavailable — a tenant mid-provisioning, a transient database blip, or a statement timeout — the endpoint returns the same empty envelope it returns when every bucket is healthy (alerts: [], total_alerts: 0) instead of a 503. The banner hides on that response and comes back on the next successful poll, so a blip reads as “no low-stock alerts” for a moment rather than as an error page. Design your own consumer the same way: treat an empty alerts array as “nothing actionable right now”, never as proof of healthy stock after a failed request.