DID Management¶
The complete lifecycle for the 30 Tata DIDs we own (91806597 8000 – 8029) and how they're routed across prod, staging, and OSS environments.
Updated 2026-04-17 after the monorepo cutover. See also:
- DID Routing Environments — how
routing_environmentcontrols dispatch- Outbound Caller ID — priority chain for CID presentation
Current allocation (convention)¶
| Range | Environment | Count | Purpose |
|---|---|---|---|
91806597 8000 – 8020 | prod | 21 | Customer DIDs + reserved |
91806597 8021 – 8024 | staging | 4 | Testing pool |
91806597 8025 – 8029 | oss | 5 | OSS demo pool |
Not hard-enforced — routing_environment column drives actual routing. Convention only.
Data model¶
CREATE TABLE did_numbers (
id CHAR(36) PRIMARY KEY,
number VARCHAR(20) UNIQUE NOT NULL, -- 08065978002 or +918065978002 or 918065978002
org_id CHAR(36), -- owning org, null if in pool
trunk_id CHAR(36),
pool_status ENUM('available','pending','assigned','reserved') DEFAULT 'available',
routing_environment ENUM('prod','staging','oss') DEFAULT 'prod', -- where inbound lands
is_default BOOLEAN DEFAULT 0, -- org's default outbound caller ID
routing_type VARCHAR(20), -- extension / queue / ivr / ai_agent
routing_destination VARCHAR(255),
recording_enabled BOOLEAN,
status ENUM('active','inactive','ported','suspended') DEFAULT 'active',
region VARCHAR(255),
provider VARCHAR(255),
monthly_price DECIMAL(10,2)
)
Key flags:
pool_statusdrives the marketplace state machine:available → pending → assigned → available(on release)routing_environmentdrives the dispatcher: prod/staging/ossis_defaultdrives the org's default outbound caller ID (at most one per org)
Lifecycle: marketplace purchase flow¶
available → (org requests) → pending → (admin approves) → assigned
└─ (admin rejects) → available
assigned → (admin releases) → available
Endpoints:
GET /api/v1/did-pool/available— org-facing, filtered by the env the caller runs inPOST /api/v1/did-pool/:id/request— org requests a DID (movesavailable → pending)POST /api/v1/did-pool/admin/:id/approve— admin approves (movespending → assigned)POST /api/v1/did-pool/admin/:id/reject— admin rejects (movespending → available)POST /api/v1/did-pool/admin/:id/assign— admin directly assigns (moves toassigned)POST /api/v1/did-pool/admin/:id/release— admin releases (moves back toavailable)
Auto-default on first assign: when an org receives its first assigned DID, autoSetDefaultIfFirst() in api/src/routes/didPool.js sets is_default=1 so new orgs always have an org default.
Admin: managing environment routing¶
Admin → DID Management (/admin/dids) shows every DID with:
- Number, Org, Region, Provider, Pool Status
- Environment dropdown — change between
Prod/Staging/OSS
Changing Environment:
- PUT
/api/v1/did-pool/admin/<id>with{ routing_environment: <env> }(server-side proxy usesINTERNAL_API_KEY) - POST
/api/v1/admin/regenerate-gateway→ regeneratesext_tata_gateway.conf+ reloads Asterisk in place - UI refreshes
OSS caveat: OSS routing is NOT dispatched from prod — it lives entirely on the NUC. Marking a DID as oss in the UI updates the flag but does NOT wire up the NUC. See OSS routing is kept on NUC for why, and how to add a new OSS DID manually.
Adding a new DID to the pool¶
Typically done once per batch. Current 30 DIDs were bulk-seeded on 2026-03-29.
INSERT INTO did_numbers (id, number, pool_status, routing_environment, status, provider, region, monthly_price)
VALUES (UUID(), '918065978030', 'available', 'prod', 'active', 'Tata', 'Bangalore', 2499);
Or via admin UI → DID Management → + Add DID.
Assigning a DID to an org (three ways)¶
(a) Org self-service (marketplace)¶
- Org dashboard → Phone Numbers → Buy a Number → click
Request - DID goes to
pending, visible in admin's Pending Requests - Admin approves → DID
assignedto requesting org - Auto-deploy triggers:
ext_<org>.confregenerated withdid_<cleannum>subroutineext_tata_gateway.confregenerated with Goto for the new DID (both Indian format aliases)- Asterisk reloaded
(b) Admin direct assign¶
- Admin → DID Management → row
Requestbutton → select org - Endpoint:
POST /api/v1/did-pool/admin/<id>/assignwith{ org_id }
(c) Manual SQL (debug only)¶
Then trigger regen: curl -X POST -H "X-Internal-Key: $KEY" http://localhost:8000/api/v1/admin/regenerate-gateway
Outbound caller ID¶
Who does a user's outbound call show up as?
Priority (first match wins):
- Per-call API
caller_id(click-to-call/originate-to-ai only) - Per-user
users.outbound_did(Users admin → edit user → Outbound Caller ID dropdown) - Org default (
did_numbers.is_default=1, set via Phone Numbers → Set as Default Caller ID) - First assigned DID by number
- NUC fallback to
+918065978001
Full walkthrough: Outbound Caller ID.
Indian format aliases (08... vs 91...)¶
Indian DIDs can be stored in local (08065978002) or international (918065978002) format. The dispatcher generator emits both routing entries for every Indian DID so customers calling either format land on the same org:
exten => 08065978002,1,Goto(org_mnd5khym__incoming,08065978002,1)
exten => 918065978002,1,Goto(org_mnd5khym__incoming,918065978002,1)
NUC's from-cloud context also normalizes 0XXXXXXXXX → 91XXXXXXXXX before its range check — so an outbound CID in local format is auto-upgraded to international before Tata sees it.
Typical trouble spots¶
| Symptom | Likely cause |
|---|---|
| Inbound call to DID lands on wrong org | Two did_numbers rows for the same physical number (e.g. one 08..., one +91...) owned by different orgs. Delete the wrong one. |
| Admin UI "Environment" change doesn't apply | Pre-Apr 17 bug: routing_environment missing from admin PUT allowed fields. Fixed in fix/did-admin-put-allow-routing-env. Verify by tailing pm2 logs astrapbx. |
| Outbound caller ID shows 78001 for every org | NUC clobbered an invalid CID. Check users.outbound_did and did_numbers.is_default for the caller. |
| Staging-flagged DID not reaching staging | cloud-endpoint-stage not Avail on prod, or WG tunnel down, or staging's tata_gateway_identify missing match=10.10.10.1. |
| New OSS DID not ringing | NUC's oss-did-route needs the entry manually. Admin UI flag alone doesn't wire OSS. |
Useful one-liners¶
# Current env distribution (run on prod or staging)
mysql -u root pbx_api_db -e "SELECT routing_environment, pool_status, COUNT(*) FROM did_numbers GROUP BY routing_environment, pool_status;"
# Which DID is an org's default caller ID?
mysql -u root pbx_api_db -e "SELECT d.number, o.name FROM did_numbers d JOIN organizations o ON d.org_id=o.id WHERE d.is_default=1;"
# Force regenerate the dispatcher
curl -X POST -H "X-Internal-Key: $(grep INTERNAL_API_KEY /opt/astrapbx/.env | cut -d= -f2)" \
http://localhost:8000/api/v1/admin/regenerate-gateway
# See what prod's dispatcher currently routes
grep -B 1 -A 3 '== [A-Z]' /etc/asterisk/ext_tata_gateway.conf