Skip to content

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:

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_status drives the marketplace state machine: available → pending → assigned → available (on release)
  • routing_environment drives the dispatcher: prod/staging/oss
  • is_default drives 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 in
  • POST /api/v1/did-pool/:id/request — org requests a DID (moves available → pending)
  • POST /api/v1/did-pool/admin/:id/approve — admin approves (moves pending → assigned)
  • POST /api/v1/did-pool/admin/:id/reject — admin rejects (moves pending → available)
  • POST /api/v1/did-pool/admin/:id/assign — admin directly assigns (moves to assigned)
  • POST /api/v1/did-pool/admin/:id/release — admin releases (moves back to available)

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:

  1. PUT /api/v1/did-pool/admin/<id> with { routing_environment: <env> } (server-side proxy uses INTERNAL_API_KEY)
  2. POST /api/v1/admin/regenerate-gateway → regenerates ext_tata_gateway.conf + reloads Asterisk in place
  3. 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)

  1. Org dashboard → Phone Numbers → Buy a Number → click Request
  2. DID goes to pending, visible in admin's Pending Requests
  3. Admin approves → DID assigned to requesting org
  4. Auto-deploy triggers:
  5. ext_<org>.conf regenerated with did_<cleannum> subroutine
  6. ext_tata_gateway.conf regenerated with Goto for the new DID (both Indian format aliases)
  7. Asterisk reloaded

(b) Admin direct assign

  1. Admin → DID Management → row Request button → select org
  2. Endpoint: POST /api/v1/did-pool/admin/<id>/assign with { org_id }

(c) Manual SQL (debug only)

UPDATE did_numbers SET org_id='<uuid>', pool_status='assigned' WHERE number='918065978010';

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):

  1. Per-call API caller_id (click-to-call/originate-to-ai only)
  2. Per-user users.outbound_did (Users admin → edit user → Outbound Caller ID dropdown)
  3. Org default (did_numbers.is_default=1, set via Phone Numbers → Set as Default Caller ID)
  4. First assigned DID by number
  5. 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 0XXXXXXXXX91XXXXXXXXX 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