Skip to main content

Trial & shared-pool number model

A trial number is a lease from a shared platform pool, not a purchase. You claim one free number, use it for a fixed 24 hours, and then either pay to keep it or let it return to the pool for the next organization. The API operations (claim, read, purchase, release) are documented endpoint-by-endpoint in Number lifecycle; this page is the concept-level model behind them β€” the caps, the timers, and the guarantees. The status vocabulary for purchased numbers lives in Number status map.

The lifetime claim: one free trial per organization, ever

Every organization gets exactly one free trial claim in its lifetime. The enforcement is a COUNT(*) over all of the organization’s pool assignments β€” active, expired, or released β€” with no rolling time window (apps/api/src/routes/numbers/number-pool.service.ts:40-48). Once the count reaches one, a further claim returns 429:
Because the lifetime rule rejects the click anyway, GET /numbers/trial carries a meta.ever_claimed flag: true once the organization’s one claim has been spent, whether or not an assignment is currently active. Clients use it to stop offering the β€œClaim a trial number” action instead of discovering the 429 at click time. Claiming also presumes you own nothing yet: an organization that already holds purchased numbers (any status other than released) is pointed at a regular purchase instead β€” the free trial exists for accounts with no numbers of their own.

The 24-hour lease and the expiry scheduler

A claim stamps the assignment with an expiresAt exactly 24 hours after claim (TRIAL_DURATION_HOURS = 24 in apps/api/src/routes/numbers/number-pool.service.ts:34). The lease is fixed: there is no extension, renewal, or longer paid hold. GET /numbers/trial returns the assignment only while it is unexpired; past expiresAt it reads null even before cleanup runs. The actual reclamation is asynchronous. A scheduler sweeps every 30 minutes (POOL_NUMBER_RELEASE_POLL_MS in apps/webhook-worker/src/worker.ts:1467), running releaseExpiredPoolNumbers (apps/webhook-worker/src/scheduler-pool-numbers.ts). Each pass:
  1. Marks every assignment whose expiresAt has passed as released.
  2. Returns the number to the shared pool and clears the previous owner, so the next claimant sees clean, unowned inventory.
  3. Removes the prior organization’s inbound routing for the number, so no configuration carries to the next claimant.
So expiry is honored within about half an hour of the 24-hour mark, and the leased digits become claimable by someone else.

The daily send cap: 50 messages per day

While the lease is active, sends routed through the trial number are capped at 50 outbound messages per day (TRIAL_DAILY_SEND_LIMIT = 50 in apps/api/src/routes/numbers/number-pool.service.ts:48) β€” a spam-control ceiling on free inventory. The counter spans the trailing 24 hours of outbound messages from that number and resets as the window rolls. Reaching the cap rejects further sends from the trial until the window drains; inbound traffic is unaffected.

Purchase to keep, or release back

Two endpoints decide the number’s fate:
  • POST /numbers/purchase-trial is the only way to keep a trial number past expiry. It promotes the same digits into your owned inventory as an active number, ends the pool assignment, and β€” when the number carries a monthly rental β€” enrolls it in the recurring-billing cycle. It is also the one rate-limited write path shared with claim: claim and purchase are limited to 5 requests per minute per tenant.
  • DELETE /numbers/release-trial ends the lease early and returns the number to the shared pool immediately, rather than waiting for the expiry sweep.
Nothing else keeps a trial alive. When the 24 hours lapse without a purchase, the scheduler reclaims the number as above, and your one lifetime claim is spent either way.

The one-hour inbound grace period

A recipient’s reply can arrive moments after you released the number or the lease expired. To keep that reply from falling into a routeless void, inbound resolution first looks for an active assignment and then falls back to any assignment released within the last hour (INBOUND_GRACE_PERIOD_MS in apps/api/src/routes/numbers/number-pool.service.ts:51). Late-arriving replies in that window still resolve to you, the prior owner. Once the hour closes β€” or a new claimant takes the number β€” inbound routes to the current owner, and the released-lease history stops matching.

Worked flow: claim, send, keep or lose

  1. Claim. POST /numbers/claim-trial β€” accepted while your lifetime count is zero and you hold no purchased numbers (claim/purchase together are rate-limited to 5 requests/min per tenant).
  2. Use. Send up to 50 messages a day, receive inbound, exercise your routing. GET /numbers/trial reports the assignment and its expiresAt.
  3. Decide. Purchase before hour 24 to keep the digits β€” after purchase the daily cap no longer applies. Miss the window and the next scheduler sweep returns the number to the pool; additional claims then 429 against the lifetime rule.

What this is not

  • Not the aging/reuse flow. The 30/45/90-day parking window, reclaim, and carrier cooldown in Number reuse, aging, and post-release posture govern purchased DIDs you release. A trial number never enters that pipeline: it goes straight back to the shared pool on release or expiry, and the 24-hour lease has no parking grace. One hour of inbound grace is the entire buffer.
  • Not a port. Porting moves a number between carriers. Trial DIDs are platform-owned inventory leased to you; the port-out model (Port-out lifecycle and ownership) applies only once you have purchased the number into your inventory.
  • Not a subscription. A purchased trial number becomes ordinary owned inventory; from then on the standard billing and renewal model applies.

Tenant-visible guarantees

  1. Exactly one free claim, enforced permanently. The lifetime count spans every assignment your organization ever held; there is no reset.
  2. Fixed expiry with bound lag. The lease lasts 24 hours, and the 30-minute sweep reclaims promptly β€” your expiresAt is a hard horizon, not a suggestion.
  3. A capped free channel. At most 50 outbound sends per rolling day from a trial number; inbound is uncapped.
  4. Only purchase preserves. There is no renew or extend path; releasing or expiring always returns the digits to the pool.
  5. Late inbound is buffered. One hour after release, replies from the lease window still resolve to you rather than erroring or leaking to the next claimant.
  6. No lease state follows the next claimant. Expiry and release both clear ownership and remove your inbound routing before the number is claimable again.

See also