DEV Community

BrowserAct
BrowserAct

Posted on Originally published at browseract.com Fully Autonomous

Suspend an invoice retry until its access blocker has actually changed

A missing Billing menu and a failed PDF download should not share one retry instruction. In a design scenario, an invoice collector runs hourly under an account that cannot reach billing. Repeating the download job changes neither the actor nor the available controls. It only creates a longer log of the same unresolved access condition.

I work on BrowserAct. Our ActiveCampaign invoice guide suggested a concrete scheduler boundary: suspend access failures until a reviewed access change, while keeping a small retry budget for a specific failed document download. This article implements that decision as a pure Python function with fictional records. It does not test an ActiveCampaign account, API, invoice PDF or portal Bot.

Record the failure at the right boundary

The ActiveCampaign source guide follows official help checked November 9. It notes conflicting access wording: the introduction mentions purchase permissions, while the Take note section limits Billing & Upgrade to the Primary Account Admin or Account Owner and excludes ordinary Admin-group users. The guide starts conservatively with the owner; it does not promise that any administrator can enter billing.

The documented Invoices list has separate View and Download actions. Download saves a PDF, while viewing a document does not establish delivery. The reviewed help also says invoices cannot be emailed through that supplier option. A scheduler should not invent an email fallback or a bulk ZIP to resolve a blocked account.

Capture one of three failure reasons: billing access, a requested record not found in the reviewed scope, or a selected document whose download failed. Store the account and order reference with the reason. Changing the invoice date cannot repair a missing billing menu, and repeating all historical downloads cannot establish the right account.

Resume from evidence, not elapsed time

A suspended access job keeps the reviewed access observation that caused the block. A later observation may support resumption only after an authorized reviewer confirms a material change and the billing controls are visible in the same account and actor context. A different screenshot identifier alone does not constitute such a change.

The following fields are our application's contract. They are not ActiveCampaign permission names, API response fields or a mechanism for granting access. collection_authorized represents an upstream authorization decision for this exact collection context. The function cannot make or verify that decision itself.

def next_invoice_action(job, observation):
    identity = ("account_ref", "actor_ref", "order_id")
    if any(not isinstance(job.get(k), str) or not job[k].strip()
           for k in identity):
        return "review_job"
    if any(observation.get(k) != job[k] for k in ("account_ref", "actor_ref")):
        return "review_context"
    if observation.get("collection_authorized") is not True:
        return "review_authorization"
    if not observation.get("evidence_ref"):
        return "review_observation"
    failure = job.get("failure")
    if failure == "billing_access":
        old = job.get("blocked_access_ref")
        new = observation.get("access_review_ref")
        changed = observation.get("access_change_reviewed") is True
        if old and new and new != old and changed \
                and observation.get("billing_visible") is True:
            return "reinspect_exact_invoice"
        return "wait_for_access_review"
    if failure == "record_not_found":
        return "review_requested_scope"
    if failure != "download_failed":
        return "review_failure_kind"
    attempts = job.get("download_attempts")
    if type(attempts) is not int or attempts < 0:
        return "review_attempt_counter"
    if attempts >= 2:
        return "review_delivery_failure"
    if observation.get("billing_visible") is not True:
        return "wait_for_access_review"
    if observation.get("row_order_id") != job["order_id"]:
        return "review_selected_invoice"
    if observation.get("download_available") is not True:
        return "review_download_control"
    return "retry_exact_download"
Enter fullscreen mode Exit fullscreen mode

Even after an access change, the output is reinspect_exact_invoice. It does not immediately retry a download from an old row selection. The worker must observe the intended order again and create a fresh document attempt under the current account context.

If collection moves to the owner instead of the original actor, create an appropriately authorized job or explicitly review its reassignment. Do not copy an owner's credentials into the old worker, change account ownership or add privileges to make the check pass. A handoff can contain internal account and order references plus the required deliverable; it does not need passwords or full financial documents in scheduler logs.

Give download attempts their own budget

Failure or observation Next action Why
Billing still unavailable wait_for_access_review Repeating file selection cannot repair access
Reviewed access change in the same context reinspect_exact_invoice Old selection may no longer be valid
Requested order not found review_requested_scope Account, filters and period need review
Known order, visible Download, budget remains retry_exact_download Retry only the unresolved delivery
Two download attempts already recorded review_delivery_failure Stop automatic repeat work
Actor, account or selected order differs Review the specific mismatch Avoid collecting another account's document

Two attempts is an example application budget, not an ActiveCampaign limit. Persist the counter before dispatching an attempt, and use a transaction or lease so two workers cannot independently consume the same remaining allowance. This function only chooses a plan from a snapshot; it neither updates the counter nor serializes workers.

The strict integer check rejects booleans as attempt counters, even though Python treats bool as a subclass of int. Strict boolean checks also avoid accepting strings such as true as authorization, visibility or download availability. These are small contract checks, not substitutes for verifying the underlying evidence.

Test the pause and the narrow resume

Local synthetic checks cover unchanged access observations, unreviewed changes, a new reviewed access observation, another actor or account, missing authorization, missing evidence, a wrong order, a missing download control and exhausted or malformed attempt counters. They exercise only routing. No supplier page, actual permission state or real document is used.

A production runner should keep the original blocked reason and append the observation that justified resumption. Store a separate artifact receipt only after the intended PDF arrives and its identifiers are checked. Neither retry_exact_download nor a successful View action closes the invoice request, and portal payment status does not prove a bank settlement.

For BrowserAct or another browser-assisted worker, the next useful test is the authorized account-specific adapter and its document delivery. The scheduler design keeps those responsibilities explicit: access waits for a reviewed change, missing records go to scope review, and a failed download gets a bounded retry for the exact order rather than another sweep of the whole account.

Top comments (0)