Skip to main content

Recovering from billing quarantine

Quarantine exists so a bad invoice does not become a bad invoice in the customer's inbox. It also means that at some point you will be staring at a child bill run full of rejected or suspect invoices, deciding what to do with them. These are the moves that make that decision fast instead of stressful.

Know your two entry points

Invoices land in quarantine one of two ways: automatically, when validation flags an invoice and the run's rejectAutoAction / suspectAutoAction is MOVE, or manually, when an operator reviews draft invoices and sends specific ones to quarantine before validating the rest. Both paths feed the same child bill run, so the recovery flow is identical either way.

POST /api/v1/billingRuns/{id}/quarantineInvoices
{ "invoiceIds": [12, 13], "description": "Carrefour amounts look wrong" }

Repeated manual quarantine calls consolidate, they do not multiply

If you call quarantineInvoices twice against the same origin bill run, both batches land in the same child quarantine BR, not two separate ones. Do not go hunting for a second quarantine run after a follow-up review pass. Check the origin BR's originBillingRunId back-reference on the existing child instead.

Manual quarantine is status-gated

quarantineInvoices only works while the origin BR sits at DRAFT_INVOICES or POSTINVOICED. Call it after the run has moved past those statuses and you get a 409, not a queued quarantine. If the UI "Send to quarantine" bulk action is missing from the Related Invoices table, that is the tell: the BR already moved on.

Read the counts before you open the review page

Every BillingRunV1Response for a quarantine BR carries a summary, so you can triage severity from a list view instead of opening every invoice:

{
"id": 542,
"isQuarantine": true,
"originBillingRunId": 540,
"quarantine": {
"rejectedInvoiceCount": 2,
"suspectInvoiceCount": 1
}
}

The Bill Runs list surfaces this as a Quarantine of #540 type column. Two rejected and one suspect is a five-minute review. Fifty rejected is a "call the offer owner" review.

Pick the release action that matches the actual problem

The quarantine review page gives you three actions per invoice. They are not interchangeable:

ActionWhat it doesUse when
RevalidateFlips status back to NEW and re-runs validationThe underlying data was fixed (BA corrected, tax mapping added)
CancelDrops the invoice; RTs stay BILLEDThe invoice was a duplicate or should never have existed
Cancel and reopen RTsDrops the invoice, reopens its RTsThe charge itself needs to be re-rated or re-billed next run

Picking Cancel when you meant Cancel-and-reopen is the single most common quarantine mistake. It leaves revenue billed but invisible, since the RTs stay BILLED and never resurface on a future bill run.

POST /api/v1/billingRuns/{id}/releaseFromQuarantine
{ "invoiceIds": [12, 13, 14], "action": "REVALIDATE", "targetBillingRunId": null }

Know the release error codes before you script against this endpoint

CodeCause
400 MISSING_INVOICE_IDSinvoiceIds is empty
400 INVALID_ACTIONaction is not REVALIDATE / CANCEL / CANCEL_RT
404 BILLING_RUN_NOT_FOUNDBR id does not exist
409BR is not a quarantine BR (isQuarantine=false)

The 409 is the one people trip over: you passed the origin BR's id instead of the child quarantine BR's id. releaseFromQuarantine only accepts the child.

Suspect invoices are often a calendar alignment problem, not a data problem

Before assuming a suspect invoice means bad customer data, check whether the charge's invoicing gate and the bill run's cutoff actually line up. A work order only becomes eligible for a bill run once its invoicingDate clears the run's cutoff. If a charge's invoicing calendar is set to the same boundary as the bill run itself (both CAL_1ST_MONTH, say), work orders created right on the boundary can be excluded or flagged rather than picked up cleanly. The fix is upstream in the charge catalog, not in the quarantine review screen: align the charge's invoicing calendar so its next eligible date falls comfortably before the bill run's cutoff, not exactly on it.

Turn off auto-quarantine deliberately, not by accident

If a bill run keeps spawning quarantine children and you would rather fail loud or fail closed, change the auto-action instead of fighting the review queue:

ActionEffect on a rejected/suspect invoice
MOVE (default)Move to a child quarantine BR
CANCELDrop the invoice
CANCEL_RTDrop the invoice, reopen its RTs
AUTOMATIC_VALIDATIONTreat as valid, continue (suspect only)
MANUAL_ACTIONStop the engine, wait for an operator

The New Bill Run form flags this inline whenever either auto-action is set to MOVE, so it is worth a second look before you launch a run you expect to be clean.