Skip to content

Diagnostic Twilio

an approved regulatory bundle is counting down to expiry

Eighteen months of German numbers working perfectly, and then one Tuesday they stop. Nothing was deployed. The bundle that proved your business address to the regulator reached its valid_until, flipped out of twilio-approved on its own, and the numbers attached to it are now non-compliant. There was a date, it was in the API the whole time, and nothing ever mentioned it.

Read-only key Python and Node.js Tests included
A woman sitting at a table looking at her cell phone
Photo by Vitaly Gariev on Unsplash
The short answer

Read GET https://numbers.twilio.com/v2/RegulatoryCompliance/Bundles?HasValidUntilDate=true&SortBy=valid-until&SortDirection=ASC and flag any bundle with status of twilio-approved whose valid_until falls inside your renewal window.

valid_until is the whole check. Approval is a snapshot of documents a regulator accepted, not a permanent state, and when the date passes the bundle is rejected without anyone acting. A bundle with a null valid_until needs no re-attestation and is not a finding — treating null as expired buries the real ones under noise.

The problem in plain words

Nothing is wrong today, and nothing will report that anything is wrong today. The bundle reads twilio-approved, the numbers send and receive, and the only evidence of the deadline is a timestamp sitting in a resource nobody reads between purchases. Meanwhile the clock is real: many national regulators require periodic re-attestation of the address and identity documents behind a number, and Twilio encodes that requirement as valid_until on the Bundle.

What makes it an outage rather than a chore is the lead time. Renewing is not a field you set. It is gathering current documents, uploading them, reassigning them to the bundle and resubmitting for review, and review takes as long as it takes. If you discover the date on the day it passes, you cannot fix it that week, and the numbers are already non-compliant and exposed to reclamation while you wait. This is the failure that ends with a country's worth of traffic gone and no deploy to blame it on.

Bundle approveddocuments acceptedonceNumbers workeighteen quietmonthsvalid_untilpassesnobody was pollingitBundle autorejectedno ticket, noemailNumbers noncompliantand subject toloss
No deploy, no ticket, no human decision. The bundle simply reached its date and the approval it carried stopped being true.

Why it happens

Approval is a snapshot, not a state. The regulator accepted a set of documents at a point in time. twilio-approved means that happened, not that it is still true, and the date on which it stops being true is the field almost nobody looks at.

The deadline lives on the bundle, not on the number. Nothing in the phone-number API changes as the date approaches. If you monitor numbers, this is invisible right up until the numbers are the thing that broke.

The transition happens without anybody acting. No human rejects the bundle. It ages out. So there is no ticket, no approval queue, no email thread — nothing that leaves the trace an ordinary compliance failure leaves.

Renewal has a lead time that a short window cannot absorb. New documents, an item assignment, a resubmission and a review. A seven-day alert on a process that takes three weeks is an alert that tells you the outage is now unavoidable.

The status callback fires too late to help. A status_callback on the bundle webhooks its transitions, which is genuinely useful and is not an early warning: it fires when the bundle has already changed state. Polling valid_until is the only thing that gives you warning rather than notification.

The fix, as a flow

The classifier keys on valid_until, because that field is the entire warning. A null value is a regulation with no renewal and a healthy bundle, and reporting it as expired buries the ones that matter.

GETRegulatoryCompliance/Bundlessorted ascending on valid-untilApproved, date far offcurrentApproved, no valid_untilno re-attestation neededInside the horizonstart the renewal nowDate already passedthe numbers are exposed
Renewal takes weeks: new documents, an item assignment, a resubmission and a review. The horizon has to be longer than that process.

How to fix it

List the bundles that have a date at all

GET https://numbers.twilio.com/v2/RegulatoryCompliance/Bundles with HasValidUntilDate=true, SortBy=valid-until and SortDirection=ASC. The sort matters: the first page is then the bundles closest to expiry, which is the report you actually want to read.

Follow v2 pagination, which is not the 2010-04-01 shape

This API returns results under results and the next page as an absolute URL in meta.next_page_url. Code written against the older account API looks for next_page_uri and a host prefix, finds neither, and stops after the first page — with the bundles sorted ascending, that at least fails in the safe direction, but only by accident.

Classify against a horizon that matches your renewal time

Compare valid_until to now. Anything already past is an incident, not a warning. The window for the rest should be as long as gathering documents and waiting for review actually takes at your organisation, which is usually sixty to ninety days rather than seven.

Check status_callback while you are in the response

An empty status_callback means the eventual transition is announced to nobody. It is not the reason this fails, but it is the reason it is discovered by a customer. Report it alongside the date rather than instead of it.

Refresh the documents, resubmit, then re-run

POST /v2/RegulatoryCompliance/SupportingDocuments for the current paperwork, reassign with POST /v2/RegulatoryCompliance/Bundles/{BundleSid}/ItemAssignments, then POST /v2/RegulatoryCompliance/Bundles/{BundleSid} with Status=pending-review. Run this audit on a schedule afterwards: the renewal you just completed sets a new valid_until, and the same date arrives again.

How to check it worked

Re-run the script. Every approved bundle should report current, with the nearest date beyond your horizon.

python3 twilio_bundle_expiry_audit.py --horizon-days 60
# 4 bundle(s), 0 expired, 0 inside the 60 day horizon

The full code

One paginated GET, sorted by the field the whole note is about, and a pure classifier that keys on valid_until. The two states worth reading carefully are no-expiry, which is a healthy bundle under a regulation that needs no re-attestation, and rejected, which is what this failure looks like from the other side of the date.

Get this script on GitHub Follow @allanninal Python and Node.js, with tests. Read only, it never writes. One of 117 Twilio fixes, free and open source.
twilio_bundle_expiry_audit.py
"""Report regulatory Bundles whose approval is about to expire.

Read only. GET requests and nothing else: give this an API Key with read access
rather than the account auth token. The repair is printed, never performed,
because resubmitting a bundle starts a review you want a human watching.
"""
import argparse
import datetime
import logging
import os
import sys

import requests

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("twilio_bundle_expiry_audit")

NUMBERS = "https://numbers.twilio.com/v2"

APPROVED = "twilio-approved"
REJECTED = "twilio-rejected"


def parse_date(value):
    """Parse an ISO 8601 timestamp from the numbers v2 API into aware UTC.

    fromisoformat on Python 3.9 does not accept a trailing Z, and a naive
    datetime compared against an aware one raises rather than returning a wrong
    answer, so both are normalised here instead of at every call site.
    """
    text = str(value or "").strip()
    if not text:
        return None
    try:
        parsed = datetime.datetime.fromisoformat(text.replace("Z", "+00:00"))
    except ValueError:
        return None
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=datetime.timezone.utc)
    return parsed.astimezone(datetime.timezone.utc)


def verdict(bundle, now, horizon_days=60):
    """Classify one Bundle on valid_until. Pure, so the dates can be tested
    without a network and without waiting eighteen months.

    valid_until is the field this whole check exists for: an approved bundle is
    approved as of a date, and when that date passes the bundle is rejected with
    nobody acting. A null valid_until is a regulation that needs no
    re-attestation, which is a healthy state and not a finding.

    Returns (state, detail).
    """
    status = str(bundle.get("status") or "").strip().lower()
    valid_until = parse_date(bundle.get("valid_until"))
    days = None if valid_until is None else (valid_until - now).days

    if status == REJECTED and days is not None and days < 0:
        return ("rejected",
                "valid_until passed %d day(s) ago and the bundle is now %s: this "
                "is the failure after the fact, and the numbers on this bundle "
                "are non-compliant today." % (-days, REJECTED))

    if status != APPROVED:
        return ("not-approved",
                "status is %s, so there is no approval to expire. That is a "
                "different problem from this one." % (status or "unset"))

    if valid_until is None:
        return ("no-expiry",
                "approved with no valid_until: this regulation does not require "
                "periodic re-attestation, so there is no date to watch.")

    if days < 0:
        return ("expired",
                "valid_until passed %d day(s) ago while the status still reads "
                "%s: the flip is not instantaneous, and the numbers on this "
                "bundle are already out of time." % (-days, APPROVED))

    if days <= horizon_days:
        return ("expiring",
                "valid_until is %d day(s) away. Renewal means new supporting "
                "documents, a reassignment and a review, so start now rather "
                "than on the date." % days)

    return ("current", "valid_until is %d day(s) away." % days)


def callback_note(bundle):
    """The reason this arrives as an outage rather than a notification, or None.

    A status_callback does not give warning, because it fires at the transition.
    It is still the difference between finding out at the moment the bundle
    changes and finding out from a customer.
    """
    if str(bundle.get("status_callback") or "").strip():
        return None
    return ("status_callback is unset: when this bundle changes state nothing is "
            "told, so the first signal will be numbers that stopped working.")


def get(session, url, **params):
    r = session.get(url, params=params, timeout=30)
    if r.status_code in (401, 403):
        raise SystemExit("%d from Twilio: check TWILIO_ACCOUNT_SID and that the "
                         "API key belongs to that account with read access"
                         % r.status_code)
    r.raise_for_status()
    return r.json()


def list_bundles(session, only_dated=True, limit=500):
    """Page the Bundles list.

    The numbers v2 API returns rows under `results` and an absolute next page in
    `meta.next_page_url`, unlike the 2010-04-01 API's `next_page_uri` path.
    Sorted ascending on valid-until so the first page is the urgent one.
    """
    url = "%s/RegulatoryCompliance/Bundles" % NUMBERS
    params = {"SortBy": "valid-until", "SortDirection": "ASC", "PageSize": 50}
    if only_dated:
        params["HasValidUntilDate"] = "true"
    out = []
    while url and len(out) < limit:
        page = get(session, url, **params)
        out.extend(page.get("results", []))
        url = (page.get("meta") or {}).get("next_page_url")
        params = {}
    return out[:limit]


def main():
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--horizon-days", type=int, default=60,
                    help="how far ahead an expiry counts as a finding")
    ap.add_argument("--all", action="store_true",
                    help="include bundles with no valid_until date")
    ap.add_argument("--as-of", default=None,
                    help="ISO date to measure against, for a reproducible run")
    args = ap.parse_args()

    account = os.environ.get("TWILIO_ACCOUNT_SID")
    key = os.environ.get("TWILIO_API_KEY")
    secret = os.environ.get("TWILIO_API_SECRET")
    if not (account and key and secret):
        log.error("set TWILIO_ACCOUNT_SID, TWILIO_API_KEY and TWILIO_API_SECRET "
                  "(an API Key with read access, not the auth token)")
        return 2

    now = parse_date(args.as_of) if args.as_of else None
    if now is None:
        now = datetime.datetime.now(datetime.timezone.utc)

    session = requests.Session()
    session.auth = (key, secret)

    bundles = list_bundles(session, only_dated=not args.all)
    if not bundles:
        log.info("no regulatory bundles with a valid_until on this account")
        return 0

    expired = soon = 0
    for bundle in bundles:
        state, detail = verdict(bundle, now, args.horizon_days)
        label = "%s/%s" % (bundle.get("iso_country") or "??",
                           bundle.get("number_type") or "?")
        line = "%-12s %s  %s  %s" % (state, bundle.get("sid", "?"), label, detail)
        if state in ("current", "no-expiry", "not-approved"):
            log.info(line)
            continue
        if state in ("expired", "rejected"):
            expired += 1
        else:
            soon += 1
        log.warning(line)
        note = callback_note(bundle)
        if note:
            log.warning("  %s", note)
        log.warning("  repair: POST %s/RegulatoryCompliance/SupportingDocuments with "
                    "current paperwork, assign it via POST %s/RegulatoryCompliance/"
                    "Bundles/%s/ItemAssignments, then POST the bundle with "
                    "Status=pending-review", NUMBERS, NUMBERS, bundle.get("sid", "?"))

    log.info("%d bundle(s), %d expired, %d inside the %d day horizon",
             len(bundles), expired, soon, args.horizon_days)
    return 1 if (expired or soon) else 0


if __name__ == "__main__":
    sys.exit(main())
twilio-bundle-expiry-audit.mjs
/**
 * Report regulatory Bundles whose approval is about to expire.
 *
 * Read only. GET requests and nothing else: give this an API Key with read
 * access rather than the account auth token. The repair is printed, never
 * performed, because resubmitting a bundle starts a review you want a human
 * watching.
 */
const NUMBERS = 'https://numbers.twilio.com/v2';

const APPROVED = 'twilio-approved';
const REJECTED = 'twilio-rejected';

/** Parse an ISO 8601 timestamp from the numbers v2 API. */
export function parseDate(value) {
  const text = String(value ?? '').trim();
  if (!text) return null;
  const ms = Date.parse(text);
  return Number.isNaN(ms) ? null : new Date(ms);
}

/**
 * Classify one Bundle on valid_until. Pure, so the dates can be tested without a
 * network and without waiting eighteen months.
 *
 * An approved bundle is approved as of a date, and when that date passes the
 * bundle is rejected with nobody acting. A null valid_until is a regulation that
 * needs no re-attestation: a healthy state, not a finding.
 *
 * Returns [state, detail].
 */
export function verdict(bundle, now, horizonDays = 60) {
  const status = String(bundle.status ?? '').trim().toLowerCase();
  const validUntil = parseDate(bundle.valid_until);
  const days = validUntil === null
    ? null
    : Math.floor((validUntil.getTime() - now.getTime()) / 86400000);

  if (status === REJECTED && days !== null && days < 0) {
    return ['rejected',
      `valid_until passed ${-days} day(s) ago and the bundle is now ${REJECTED}: ` +
      'this is the failure after the fact, and the numbers on this bundle are ' +
      'non-compliant today.'];
  }

  if (status !== APPROVED) {
    return ['not-approved',
      `status is ${status || 'unset'}, so there is no approval to expire. That is ` +
      'a different problem from this one.'];
  }

  if (validUntil === null) {
    return ['no-expiry',
      'approved with no valid_until: this regulation does not require periodic ' +
      're-attestation, so there is no date to watch.'];
  }

  if (days < 0) {
    return ['expired',
      `valid_until passed ${-days} day(s) ago while the status still reads ` +
      `${APPROVED}: the flip is not instantaneous, and the numbers on this bundle ` +
      'are already out of time.'];
  }

  if (days <= horizonDays) {
    return ['expiring',
      `valid_until is ${days} day(s) away. Renewal means new supporting documents, ` +
      'a reassignment and a review, so start now rather than on the date.'];
  }

  return ['current', `valid_until is ${days} day(s) away.`];
}

/** The reason this arrives as an outage rather than a notification, or null. */
export function callbackNote(bundle) {
  if (String(bundle.status_callback ?? '').trim()) return null;
  return 'status_callback is unset: when this bundle changes state nothing is ' +
         'told, so the first signal will be numbers that stopped working.';
}

function authHeader(key, secret) {
  return `Basic ${Buffer.from(`${key}:${secret}`).toString('base64')}`;
}

async function get(auth, url, params = {}) {
  const u = new URL(url);
  for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);
  const res = await fetch(u, { headers: { Authorization: auth } });
  if (res.status === 401 || res.status === 403) {
    throw new Error(`${res.status} from Twilio: check TWILIO_ACCOUNT_SID and ` +
                    'that the API key belongs to that account with read access');
  }
  if (!res.ok) throw new Error(`${res.status} from ${u.pathname}`);
  return res.json();
}

/**
 * The numbers v2 API returns rows under `results` and an absolute next page in
 * `meta.next_page_url`, unlike the 2010-04-01 API's `next_page_uri` path.
 */
export async function listBundles(auth, onlyDated = true, limit = 500) {
  let url = `${NUMBERS}/RegulatoryCompliance/Bundles`;
  let params = { SortBy: 'valid-until', SortDirection: 'ASC', PageSize: 50 };
  if (onlyDated) params.HasValidUntilDate = 'true';
  const out = [];
  while (url && out.length < limit) {
    const page = await get(auth, url, params);
    out.push(...(page.results ?? []));
    url = page.meta?.next_page_url ?? null;
    params = {};
  }
  return out.slice(0, limit);
}

async function main() {
  const account = process.env.TWILIO_ACCOUNT_SID;
  const key = process.env.TWILIO_API_KEY;
  const secret = process.env.TWILIO_API_SECRET;
  if (!account || !key || !secret) {
    console.error('set TWILIO_ACCOUNT_SID, TWILIO_API_KEY and TWILIO_API_SECRET ' +
                  '(an API Key with read access, not the auth token)');
    process.exitCode = 2;
    return;
  }

  const flag = process.argv.indexOf('--horizon-days');
  const horizonDays = flag === -1 ? 60 : Number.parseInt(process.argv[flag + 1], 10);
  const now = new Date();

  const auth = authHeader(key, secret);
  const bundles = await listBundles(auth, !process.argv.includes('--all'));
  if (bundles.length === 0) {
    console.log('no regulatory bundles with a valid_until on this account');
    return;
  }

  let expired = 0;
  let soon = 0;
  for (const bundle of bundles) {
    const [state, detail] = verdict(bundle, now, horizonDays);
    const label = `${bundle.iso_country ?? '??'}/${bundle.number_type ?? '?'}`;
    const line = `${state.padEnd(12)} ${bundle.sid ?? '?'}  ${label}  ${detail}`;
    if (state === 'current' || state === 'no-expiry' || state === 'not-approved') {
      console.log(line);
      continue;
    }
    if (state === 'expired' || state === 'rejected') expired += 1; else soon += 1;
    console.warn(line);
    const note = callbackNote(bundle);
    if (note) console.warn(`  ${note}`);
    console.warn(`  repair: POST ${NUMBERS}/RegulatoryCompliance/SupportingDocuments ` +
                 `with current paperwork, assign it via POST ${NUMBERS}/` +
                 `RegulatoryCompliance/Bundles/${bundle.sid ?? '?'}/ItemAssignments, ` +
                 'then POST the bundle with Status=pending-review');
  }

  console.log(`${bundles.length} bundle(s), ${expired} expired, ${soon} inside the ` +
              `${horizonDays} day horizon`);
  process.exitCode = (expired || soon) ? 1 : 0;
}

// Only run when invoked directly. The test file imports this module, and without
// the guard main() would run there too, fail on the missing credentials and set a
// non-zero exit code that fails the whole test file even as every test passes.
if (import.meta.url === `file://${process.argv[1]}`) {
  main().catch((err) => { console.error(err.message); process.exitCode = 2; });
}

Add a test

Pinning a date-based rule means pinning the clock, so every case runs against a fixed now. The two that matter most are the ones a naive check gets wrong in opposite directions: a bundle with a null valid_until must not be reported as expired, and a bundle already past its date must not be quietly filed under the horizon warning it has outrun.

test_twilio_bundle_expiry_audit.py
import datetime

from twilio_bundle_expiry_audit import callback_note, parse_date, verdict

NOW = datetime.datetime(2026, 8, 30, tzinfo=datetime.timezone.utc)


def make(**kw):
    bundle = {"sid": "BU00000000000000000000000000000001",
              "friendly_name": "DE local address",
              "iso_country": "DE",
              "number_type": "local",
              "status": "twilio-approved",
              "valid_until": "2027-06-01T00:00:00Z",
              "status_callback": "https://ops.example.com/bundle"}
    bundle.update(kw)
    return bundle


def test_iso_dates_from_the_numbers_v2_api_parse_with_a_trailing_z():
    assert parse_date("2027-06-01T00:00:00Z") == \
        datetime.datetime(2027, 6, 1, tzinfo=datetime.timezone.utc)


def test_an_approved_bundle_far_from_its_date_is_current():
    state, detail = verdict(make(), NOW, horizon_days=60)
    assert state == "current"
    assert "275 day(s)" in detail


def test_an_approved_bundle_inside_the_horizon_is_the_warning():
    state, detail = verdict(make(valid_until="2026-09-15T00:00:00Z"), NOW,
                            horizon_days=60)
    assert state == "expiring"
    assert "16 day(s)" in detail


def test_the_horizon_is_the_thing_that_decides():
    bundle = make(valid_until="2026-10-20T00:00:00Z")
    assert verdict(bundle, NOW, horizon_days=30)[0] == "current"
    assert verdict(bundle, NOW, horizon_days=60)[0] == "expiring"


def test_a_date_already_past_is_an_incident_not_a_warning():
    state, detail = verdict(make(valid_until="2026-08-01T00:00:00Z"), NOW)
    assert state == "expired"
    assert "29 day(s) ago" in detail


def test_the_aftermath_reads_as_rejected_rather_than_as_expired():
    state, detail = verdict(make(status="twilio-rejected",
                                 valid_until="2026-07-01T00:00:00Z"), NOW)
    assert state == "rejected"
    assert "non-compliant today" in detail


def test_a_null_valid_until_is_healthy_and_must_not_be_read_as_expired():
    state, detail = verdict(make(valid_until=None), NOW)
    assert state == "no-expiry"
    assert "re-attestation" in detail


def test_a_bundle_that_was_never_approved_is_somebody_else_s_note():
    state, _ = verdict(make(status="pending-review", valid_until=None), NOW)
    assert state == "not-approved"


def test_a_missing_status_callback_is_reported_alongside_the_date():
    assert callback_note(make(status_callback="")) is not None
    assert callback_note(make()) is None
twilio-bundle-expiry-audit.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { callbackNote, parseDate, verdict } from './twilio-bundle-expiry-audit.mjs';

const NOW = new Date('2026-08-30T00:00:00Z');

const make = (over = {}) => ({
  sid: 'BU00000000000000000000000000000001',
  friendly_name: 'DE local address',
  iso_country: 'DE',
  number_type: 'local',
  status: 'twilio-approved',
  valid_until: '2027-06-01T00:00:00Z',
  status_callback: 'https://ops.example.com/bundle',
  ...over,
});

test('iso dates from the numbers v2 api parse with a trailing z', () => {
  assert.equal(parseDate('2027-06-01T00:00:00Z').toISOString(),
               '2027-06-01T00:00:00.000Z');
});

test('an approved bundle far from its date is current', () => {
  const [state, detail] = verdict(make(), NOW, 60);
  assert.equal(state, 'current');
  assert.match(detail, /275 day\(s\)/);
});

test('an approved bundle inside the horizon is the warning', () => {
  const [state, detail] = verdict(make({ valid_until: '2026-09-15T00:00:00Z' }), NOW, 60);
  assert.equal(state, 'expiring');
  assert.match(detail, /16 day\(s\)/);
});

test('the horizon is the thing that decides', () => {
  const bundle = make({ valid_until: '2026-10-20T00:00:00Z' });
  assert.equal(verdict(bundle, NOW, 30)[0], 'current');
  assert.equal(verdict(bundle, NOW, 60)[0], 'expiring');
});

test('a date already past is an incident not a warning', () => {
  const [state, detail] = verdict(make({ valid_until: '2026-08-01T00:00:00Z' }), NOW);
  assert.equal(state, 'expired');
  assert.match(detail, /29 day\(s\) ago/);
});

test('the aftermath reads as rejected rather than as expired', () => {
  const [state, detail] = verdict(make({
    status: 'twilio-rejected', valid_until: '2026-07-01T00:00:00Z' }), NOW);
  assert.equal(state, 'rejected');
  assert.match(detail, /non-compliant today/);
});

test('a null valid_until is healthy and must not be read as expired', () => {
  const [state, detail] = verdict(make({ valid_until: null }), NOW);
  assert.equal(state, 'no-expiry');
  assert.match(detail, /re-attestation/);
});

test('a bundle that was never approved is somebody else\'s note', () => {
  assert.equal(
    verdict(make({ status: 'pending-review', valid_until: null }), NOW)[0],
    'not-approved');
});

test('a missing status_callback is reported alongside the date', () => {
  assert.notEqual(callbackNote(make({ status_callback: '' })), null);
  assert.equal(callbackNote(make()), null);
});

FAQ

What actually happens to the numbers when the bundle expires?

They become non-compliant with the regulation the bundle satisfied, which puts them at risk of being reclaimed. The timing after that is not a published SLA and it is not something to plan around: treat valid_until as the deadline and the period after it as borrowed time, not as a grace period you can schedule work in.

How long should the horizon be?

As long as your renewal actually takes end to end. Gathering current documents from a legal or finance team, uploading them, reassigning them and waiting for review is measured in weeks, so sixty to ninety days is a realistic window. A seven-day alert on a three-week process is a notification that the outage is now unavoidable.

Why is the status still twilio-approved after the date has passed?

Because the status changes when Twilio processes the expiry, not at the stroke of midnight in the field. That is why the script keys on the date rather than waiting for the status, and why an approved bundle with a date in the past gets its own state: it is the last moment at which the problem is still cheap.

Can I use the status callback instead of polling?

Set it, and do not rely on it for this. A status_callback webhooks the bundle's transitions, so it tells you at the moment the bundle stops being approved, which is the moment the numbers are already exposed. Polling valid_until is what buys you the weeks you need. The callback is how you find out about everything else that happens to the bundle.

What about bundles with no valid_until at all?

They are fine, and reporting them is actively harmful. Many regulations require a one-time attestation with no renewal, so a null date means there is nothing to watch. Treating null as expired fills the report with noise and hides the four bundles that genuinely have a date coming.

Related field notes

Sources

Every figure in this note is traced to one of these. Prices are list rates and change — check them for your own region before acting.

Stuck on a tricky one?

If your setup is misbehaving in a way this note does not cover, message me on LinkedIn with what you are seeing.