Skip to content

13 min read

When an appeal is refused

On this page

A refused appeal is a different object from a slow one. While recourse is pending, a routing rule that sends the affected cohort to a differently configured deployment is a bridge: justified by "hold the line until the appeal resolves", scoped narrowly, given an expiry date and an owner. When the reply comes back declining a configuration change, the appeal has resolved. It has resolved against you, which means the bridge's justification has evaporated while the bridge itself is still standing and carrying live traffic.

Everything the bridge was allowed to defer now has to be answered. A relaxed severity threshold means one customer's traffic is processed under weaker content safeguards than the rest of the product — defensible as a five-day mitigation, a documented position when it becomes the design. If the fallback is a different provider or a different region, the residency and sub-processor questions — a sub-processor being any third party you pass customer data to under your own contract, and one your customer usually has the right to approve — that were acceptable to postpone for a week become contractual questions with a compliance owner. If it is a different model, the quality difference the customer has not yet complained about becomes something they are entitled to be told about. And the rule has an expiry date; the realistic outcomes are that somebody extends it every quarter forever, or that it lapses one Tuesday and the blocked class starts failing again with nobody watching.

A refused appeal converts an incident into a product constraint, and product constraints are not on-call work.

The options, and what each one actually costs

There are three, and the shape of the choice does not change much across domains.

Route elsewhereRe-scope the featureDeclare unsupported
What it meansAffected cohort served by another provider, a self-hosted model, or a differently configured deploymentThe blocked content class never reaches that model — filter it out, handle it separately, or narrow what the feature claims to doTell the customer this workload is not supported; amend or exit the contract
Time to stand upDays if a second provider is already integrated; 6–12 weeks for self-hosting from nothing2–6 weeks, mostly product and domain review rather than codeDays of work, months of negotiation
Ongoing costSecond vendor relationship, or GPU capacity and an on-call rotation for inferenceLower feature value; more manual work for the customer's staffLost revenue; possible refund or early-termination terms
Residual riskYou now own the safety posture you were rentingThe excluded class is exactly the customer's hardest workCustomer churn; reference risk in a small market
ReversibleYes, with effortYesNo
Decided byEngineering proposes, commercial and legal approveProduct, with domain sign-offCommercial, with legal

The self-hosting row deserves the sharpest look, because it is the one engineers reach for and the one whose cost is most consistently understated. Moving a cohort to an open-weights model — one whose parameters are published for download, so it can run on hardware you control — does remove the provider's classifier from the path. It does not remove the requirement for a classifier; it transfers it to you, along with the decision about where the thresholds sit, the obligation to defend those thresholds to a regulator or an enterprise security review, and the incident response for whatever the model produces. You are not escaping content policy; you are becoming the party that writes it. That is a legitimate choice, made by companies with the domain expertise to make it, and it is not a choice an on-call engineer makes to close a ticket.

The raw arithmetic is usually the least interesting input, and it is worth computing anyway so the conversation has a floor:

python
# Affected cohort only: 12,000 documents/month, 6k in / 1.2k out per document.
DOCS, IN_TOK, OUT_TOK = 12_000, 6_000, 1_200
api_monthly = DOCS * (IN_TOK * 3.00/1e6 + OUT_TOK * 15.00/1e6)      # $432

# Self-hosted: two A100-class instances for redundancy, reserved.
gpu_hourly, instances = 2.10, 2
infra_monthly = gpu_hourly * instances * 730                         # $3,066
eng_monthly   = 0.25 * 14_000                                        # $3,500 (quarter of an engineer)
print(api_monthly, infra_monthly + eng_monthly)                      # 432.0  6566.0

Each of those three lines is one multiplication, and the interesting part is not their sizes but their shapes. The API line is volume times unit price: six thousand input tokens at three dollars a million and twelve hundred output tokens at fifteen dollars a million come to thirty-six thousandths of a dollar per document, and twelve thousand documents at that rate is the $432. The infrastructure line is capacity times time: two instances at the hourly rate in the code above, held for the seven hundred and thirty hours in an average month, is $3,066, and not one term in that product mentions a document. The engineering line is a fraction of a salary, which mentions documents even less.

That difference in shape matters more than the ratio does, because it says how each option behaves when the volume changes. Halve the traffic and the API bill halves; the GPUs cost the same, because reserved capacity is billed for the hours it is reserved whether or not anything arrives. Double the traffic and the API bill doubles while the GPU bill does not move until a third instance is needed. So one of these is a line through the origin and the other is a high floor that barely tilts, and at this cohort's volume the floor is far above the line. Somewhere well above twelve thousand documents a month they cross. Whether that crossing is relevant depends on the growth the commercial side is planning for, which is one more reason this is their decision and not yours.

Fifteen times the cost, for one cohort, before the twelve weeks of build. That number does not decide anything on its own — if the cohort represents a £400k contract, $6,566 a month is obviously worth it, and if it represents three self-serve users it obviously is not. What the number does is stop the conversation happening in the register of "we could just run our own model", which is where it starts when nobody has written it down.

Engineering supplies the constraint, not the choice

The failure mode is not that the wrong option gets chosen. It is that no option gets chosen, because the bridge keeps working and nobody escalates, and the organisation drifts into the most expensive option by default with no record of having decided anything.

The reason nobody escalates deserves naming, because it is not indifference. A working mitigation removes the signal that produced the urgency. Every instrument this topic built — the segmented block rate, the alert on a cohort class, the customer's phone calls — is measuring the symptom, and the routing rule fixed the symptom. So the block rate on that endpoint reads normal, the alert stays quiet, the tickets stop, and each of those is correct. What none of them measures is the thing that is still true: one customer's traffic is being served under a configuration nobody signed off. A mitigation converts a loud problem into a silent one, which is what mitigations are for, and it means the prompt to decide has to be manufactured rather than waited for.

Avoiding the drift therefore requires the engineering side to produce a specific artefact and then stop. The artefact is short and it is not a design document.

markdown
## DEC-2026-08-19 — Clinical summarisation blocked by provider policy (violence)

**Constraint.** The provider's `violence` classifier blocks ~8% of this
customer's uploads (documents describing patient aggression toward staff).
Appeal filed 2026-08-07, refused 2026-08-14: "operating as intended, no
configuration change". Reproduction: fixture PB-2026-08-14-03. Not a defect
in our software; no engineering change makes it pass.

**Currently.** Temporary routing to `gpt-4o-clinical-eu-relaxed` (INC-4471),
expires 2026-09-15. Weaker safeguards than the rest of the product; not
reviewed by compliance. If it lapses, 8% of uploads fail again.

**Exposure.** 960 documents/month. Contract carries 99.5%; error budget is
60/month. ARR (annual recurring revenue) £412k. Two further healthcare
prospects in pipeline with the same document mix.

**Options.** (1) Make the routing permanent — needs compliance sign-off on
the relaxed threshold, ~1 week. (2) Self-host for this cohort — ~12 weeks,
~$6.6k/month, we own the safety posture. (3) Re-scope: exclude aggression
reports, customer's staff summarise ~32/day manually. (4) Declare the
workload unsupported and renegotiate.

**Decision needed by** 2026-09-08 (one week before the routing expires).
**Decision owner:** VP Product, with Legal (2, 4) and Compliance (1).
**Engineering recommendation:** (1) now, (2) if the healthcare segment grows.

Everything in that memo is something only engineering can supply: the constraint is real and not a bug, the reproduction exists, the numbers are measured rather than estimated, the current state has an expiry, and each option has a cost and a named approver. What it deliberately does not contain is a decision, because the trade being made — safety posture against revenue against build cost — is not an engineering trade and engineering does not have the standing to make it.

Two things the engineering side should refuse. The first is silently building a euphemism layer: rewriting the customer's professional language until it stops scoring, so the block goes away and the output is produced. This is the tempting one, because it looks like the clever technical fix and it can be shipped in a day. It means an automated system is altering the substance of a record to satisfy a classifier, unreviewed by anyone qualified, and the failure mode is a summary that quietly omits the thing the document was written to report.

It is worth being clear that this one would work, mechanically, which is exactly why it needs refusing rather than debunking. The score is continuous and the threshold is a cut point, so there is always some rewriting that moves an input below the line, and the reduction machinery from earlier in this topic finds it efficiently. The block rate would drop. The dashboards would go green. What the mechanism cannot tell you is what was removed to achieve that, because the thing being optimised is a classifier's score and not the accuracy of the record — and in a clinical incident report the passage carrying the most score is, reliably, the passage describing the incident. A system tuned to minimise policy blocks on safety documentation converges on documentation that does not describe safety events. Nothing in the code would ever report that; it is the intended behaviour of the objective somebody chose. The second is letting the mitigation become permanent by omission — the same failure in slower motion.

Making the decision actually happen

Writing the memo is the easy part. Organisations are extremely good at not deciding things that are currently working, and a bridge that carries traffic is, by construction, currently working. The mechanism that converts a memo into a decision is the expiry date, and it only works if the expiry is real.

What an expiry changes is which outcome you get for free. Without one, doing nothing preserves the current arrangement at no cost to anybody, so the option requiring no meeting, no compliance review and no budget is also the option that wins by default — and it wins silently, since nothing happens on the day it wins. A real expiry inverts that. Doing nothing now produces an event: the rule lapses, the blocked class starts failing again, and somebody's phone rings. Inaction stops being free, which is the only thing that reliably moves a decision nobody wants to make. Everything in the paragraph below follows from that one property, which is also why the ways an expiry can be unreal are worth enumerating: each of them quietly restores the default the date was there to remove.

Real means the routing rule genuinely lapses on the date in the configuration, that the lapse is loud, and that everybody knows in advance what it will do. A rule that quietly renews, or that expires into a silent failure rate on a regulated customer, teaches the organisation two opposite and equally bad lessons. The workable pattern is an expiry that fires an alert a fortnight ahead, a second one a week ahead naming the decision owner, and — on the day, if nothing has been decided — an extension granted explicitly by that named owner, recorded, with a new date. Extending is a legitimate outcome. Extending without anyone signing their name to it is how a constraint becomes permanent architecture that nobody chose.

The escalation ladder matters as much as the deadline. If the decision owner does not respond by the second reminder, the memo goes up one level with the fact of non-response attached, because "we asked and nobody answered" is itself information the next level up needs. Engineers are often reluctant to do this, reading it as going over someone's head; it is closer to the opposite, since the alternative is engineering silently absorbing a decision that was assigned elsewhere.

One boundary is worth stating explicitly, because it cuts against everything above. Engineering does not own the commercial trade, and it does own the technical claim underneath it. If a proposed option requires asserting something untrue — that a relaxed deployment is equivalent to the standard one, that a self-hosted path has safeguards it does not yet have, that a partial summary is complete — that is not a business decision to defer to. Say so in writing, in the memo, next to the option it applies to. The distinction is between refusing to choose and refusing to misrepresent, and only the second is yours to insist on.

Provider policy is not a stable substrate

None of the four options is a permanent answer. Classifiers are retrained, thresholds move, categories are added, and a workload that is unsupportable this quarter may be routine next year — or the reverse, which is why the fixture suite keeps running against whichever path is chosen. A decision recorded in August is a decision about August's policy.

The market is moving under this too. Content-policy configurability, category-level documentation, and the existence of a real appeal path are increasingly items on enterprise procurement checklists rather than things discovered in production; regulated buyers in health, legal and security have started asking for them by name during vendor selection. The organisational lesson from a case like this is usually less "we picked the wrong model" than "we did not ask about policy configurability before signing", and the cheapest version of this whole topic is a question in a vendor evaluation.

And the constraint is not always unreasonable, which is the part worth sitting with. A classifier that blocks detailed descriptions of violence is doing something defensible in general, and the fact that it is wrong for a clinical incident-reporting workload does not make it wrong. The provider's answer — operating as intended — may be literally correct and still leave your customer unable to work. That is what a shared safety layer is: a policy set for a population you are one member of, applied to your traffic, tuned for risks that are not yours.

What you have at the end of this topic is the loop that makes that survivable. Blocks are separated from refusals at the boundary and never retried. Every one carries the provider's category, stage and request id into a record you can query and a reference the user can quote. Reproductions become synthetic fixtures that run nightly and tell you when policy moves in either direction. The rejection rate is segmented finely enough that a forty-user cohort failing is visible while it is happening rather than eleven days later. The blocked user gets a message that is true. And when recourse runs out, the trade goes to the people whose job it is to make it, in writing, with a date on it. Run that loop on the next provider you integrate, before the first incident rather than after, and the failure that would have taken eleven days takes an afternoon.

Provider content filters as an operational problem — read the current version at https://daviddominguez.dev/learn/en/provider-content-filters-as-an-operational-problem/when-an-appeal-is-refused

Back to top