Twilio's public Messages API documents a fixed set of message status values, and the initial value depends on how the message was created - accepted when a Messaging Service is used, queued when it is not. Every customer who writes an exhaustive match over that list inherits a dependency on it. What does publishing a code list as part of an API force on its owner, and where would copying that approach be a mistake?
Show the full answer Hide the answer
The situation they are in
Twilio's messaging API exposes delivery progress as a status string. The documented values include
accepted, scheduled, canceled, queued, sending, sent, delivered, undelivered, failed,
receiving, received and a read value available only on some channels (Twilio Messages
resource and status-callback
documentation, read 2026-10). Two properties of that list are the whole lesson. It is public, so
Twilio cannot change it on its own schedule. And the first value a customer observes depends on a
configuration choice they made — accepted with a Messaging Service, queued without one — so two
customers of the same API see different opening states for the same operation.
What publishing a code list forces on the owner
- Values become append-only. Redefining an existing value silently changes the meaning of history for every consumer that stored it. Adding a value only breaks consumers who wrote an exhaustive match, which is a smaller and more diagnosable population.
- Terminal versus intermediate must be documented, not inferred. A consumer's retry and billing logic keys off whether a state can still change. If the vendor does not say, every consumer guesses, and they guess differently.
- The list must be machine-readable. A table in prose means every client library hard-codes its own copy and they drift. An enumeration in the OpenAPI description or an endpoint that returns the current list lets a consumer detect an unknown value as data rather than as a crash.
- Someone owns the list, not the feature. Status values are added by whoever ships the feature that
needs them, so without a single reviewer the list accumulates near-synonyms.
failedandundeliveredlook like duplicates until you read that one means Twilio could not send and the other means a carrier reported non-delivery.
What it costs them
The list can only grow, so the vendor carries every value ever shipped for as long as the API version lives, including channel-specific states that make no sense for SMS. Consumers pay too: the correct client is one that treats an unrecognised status as "not terminal yet" and logs it, which is more code than a switch statement and is the reason most integrations are subtly wrong on new values.
Where copying it would be a mistake
Two teams in one repository should not build a published reference-data service for their shared statuses. A single enumeration type with a compile-time exhaustiveness check is stronger than any registry: a new value breaks the build of every consumer in the same pull request, which is exactly the behaviour a published list gives up. The registry, the effective dates and the change notice only start paying when consumers deploy on their own schedule and you cannot see their code — which is Twilio's situation and not an internal team's.
The second mistake is applying append-only discipline to operational code lists that are genuinely local. A list of 60 internal job states used by one service and nothing else can be rewritten freely, and treating it as governed reference data adds a change process to a decision one team is entitled to make.
Common weak answers
- "Version the API when you add a status." A new minor value does not justify a new API version, and versioning does not help the consumer who stored the old value in a database. The storage problem is solved by append-only values, not by versioning.
- "Validate with an enum in the client library." A strict enum turns an added value into a parse failure — it converts a vendor's additive change into your outage.