If you're still buying numbers by emailing a spreadsheet of area codes and waiting for a reply the next morning, you already know the problem. It doesn't scale, it breaks on weekends, and when a campaign goes live Monday at 8am and half the numbers are flagged by Tuesday, you can't rotate fast enough to matter.
This is a post about what a real bulk DID API should do, what to interrogate before you sign anything, and where providers quietly cut corners.
Why email provisioning falls apart past a certain volume
Email works fine when you're ordering 20 numbers a month. It stops working the moment any of these are true:
- You need to rotate flagged numbers daily
- You're buying across more than a handful of area codes at a time
- You have a dialer that expects numbers provisioned into SIP trunks without a human in the middle
- Your customers (if you're reselling) expect self-serve ordering
The failure mode isn't dramatic. It's just that your ops person spends four hours a day in a ticket queue, and your answer rates degrade because you can't swap numbers fast enough. If you're running local presence at any real scale, the API isn't optional — it's the whole point.
What a working bulk DID API should actually do
Forget the marketing page. Here's the functional checklist. If a provider can't answer yes to all of these with documentation you can read in a browser, keep looking.
Search inventory by area code and rate center
You should be able to hit an endpoint like:
GET /v1/numbers/available?npa=404&quantity=100
GET /v1/numbers/available?rate_center=ATLANTA&quantity=250
And get back actual available numbers — not a promise that someone will check and get back to you. Rate center matters more than area code for presence dialing; two numbers in the same NPA can land in different rate centers and get treated differently downstream.
Purchase and assign in a single call
You shouldn't buy a number, wait, then make a second call to point it at your SIP trunk. One call should do both:
POST /v1/numbers/order
{
"numbers": ["4045551212", "4045551213", ...],
"trunk_id": "trunk_abc123",
"sms_enabled": false
}
Response should include which numbers succeeded, which failed, and why. Partial-success handling is where cheap APIs fall over — if 3 of 100 numbers were already taken between search and purchase, the API shouldn't fail the whole batch.
Release numbers programmatically
If you can't release flagged numbers via API, you're paying rent on dead inventory. There should be a straightforward DELETE and the billing should stop the same day.
Webhooks for state changes
Port completion, DID activation, 911 registration, SMS enablement — all of these should fire webhooks. Polling an endpoint every 60 seconds to find out if your port went through is 2012 behavior.

Porting: the real stress test
Anyone can ship an API that sells fresh numbers. Porting is where you find out if a provider is serious.
Ask specifically:
- Can I submit a port request via API with the LOA attached, or do I still have to email it?
- Do I get a FOC date back in the API response, or do I have to ask?
- What happens when the losing carrier rejects? Do I see the rejection reason in the API, or does it sit in someone's inbox for a day?
- Can I port in 500 numbers in one submission, or do I have to break it into batches of 50?
The honest answer from most providers is "porting is still partly manual." That's fine — LOAs are a human-signature thing and losing carriers do what they do. What's not fine is a provider who can't even tell you the status without you opening a ticket. More on the mechanics in this breakdown of porting turnaround.
Rate limits, idempotency, and the stuff nobody tells you
This is where provisioning APIs quietly fail in production.
Rate limits. If you're buying 500 numbers in a burst, does the API throttle you at 10 requests per second? Fine, but you need to know. The docs should state it plainly.
Idempotency keys. If your network blips mid-order, you need to be able to retry safely without buying 200 numbers twice. Any API without idempotency keys on write endpoints is not ready for bulk use.
Error schemas. Errors should be machine-readable. {"error": "number_unavailable", "number": "4045551212"} is useful. {"error": "Something went wrong"} is not.
Sandbox. You should be able to test the full order flow without actually buying numbers. If the only way to try the API is with real money and real DIDs, that tells you how much the provider respects developer time.
Questions to ask before you commit
When you're evaluating a provider, these are the questions that separate the real ones from the resellers of resellers:
- Show me the API docs. Not a PDF — the live docs. If they're behind a sales call, that's your answer.
- What's the activation time from
POST /orderto the number ringing on my trunk? Seconds, minutes, or "within an hour"? - Can I assign numbers to different SIP trunks or sub-accounts via API, or is that a support ticket?
- What's your rejection rate on port-ins last quarter? If they can't answer, they're not watching.
- When a number gets flagged as spam-likely, what's your release-and-replace workflow look like? (Rotation strategy matters as much as the API — see this piece on reserve pools.)
- If I need 1,000 numbers across 40 area codes tomorrow, is that a normal order or a special request?
The last one is the tell. For a serious bulk provider, 1,000 numbers across 40 NPAs is Tuesday. For a repackaged reseller, it's a week of back-and-forth.
Pricing conversations
Don't try to compare providers on a sticker rate. Ask for the full rate sheet — DID monthly, per-minute inbound, per-minute outbound by jurisdiction, SMS if you need it, 911 fees, CNAM fees, port-in and port-out fees. Compare line by line. A provider with a low DID rate and high per-minute is not a bargain if you're running a dialer. There's more on reading rate decks here.
If you're committing volume, negotiate. Nobody publishes their real bulk rate.
What to do next
Short list of three or four providers. Get sandbox credentials from each. Write a 50-line script that searches, orders 10 numbers, assigns them to a trunk, makes a test call, and releases them. The provider whose script you finish first — and whose numbers actually ring — is probably your answer.
And when you're ready to talk volume, come in with a specific ask: how many numbers, which area codes, what dial pattern, what porting load. Vague requests get vague quotes.