Navigation

Submitting Scraping Feedback

When a scraping run produces a user-visible result — correct or incorrect — surface a “was this right?” affordance and submit the answer through POST /v1/scraping-runs/:run_id/feedback. Feedback closes the loop between what you observe in production and the agent self-improvement pipeline.

The four signals

The endpoint accepts a small, deliberately narrow vocabulary. These are the four states a client (or its end-user) can actually observe:

signalUse when…
succeededBill amount + due date + account match the user’s statement. Affirmative feedback is just as valuable as negative — it lets us auto-promote winning prompt versions.
login_failedLogin failed despite the user confirming valid credentials (often a biller DOM change).
bill_missingStatement is visible on the biller site but the agent returned no bill.
data_incorrectThe bill came back but a field (amount, due date, account, etc.) doesn’t match the source statement. Put the specifics in notes.

System-only signals (timeout, two_factor_required, extraction_empty) are tracked internally and are NOT in the client enum. Clients can’t meaningfully observe them.

1. Capture run_id + biller_id

Every scraping run returns a run_id and is associated with a biller_id. Persist both alongside the bill you display so feedback can be attributed back to the run that produced it and the biller it targeted.

When the user reports a problem on that bill, the report carries the run + biller reference.biller_id is required (it’s also what keys the per-biller rate limit).

2. POST the feedback

On user submission, POST to the endpoint with an Idempotency-Key so a network retry doesn’t create duplicate feedback. The key should be generated once at the moment the user clicks submit and re-sent on retry. Recommended format: idem_<timestamp>_<run_id>.

Minimal client-side submission
Node
async function submitFeedback({ runId, billerId, signal, notes }) {
  // Generated ONCE here. If the network errors and you retry, reuse this same key.
  const idempotencyKey = `idem_${Date.now()}_${runId}`;

  const submit = async () => fetch(
    `https://sandbox.api.billerapi.com/v1/scraping-runs/${runId}/feedback`,
    {
      method: 'POST',
      headers: {
        'X-Client-ID': process.env.BILLERAPI_CLIENT_ID,
        'X-Client-Secret': process.env.BILLERAPI_CLIENT_SECRET,
        'Idempotency-Key': idempotencyKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        signal,                   // 'succeeded' | 'login_failed' | 'bill_missing' | 'data_incorrect'
        biller_id: billerId,      // required
        notes,                    // optional, recommended for data_incorrect
      }),
    },
  );

  let res = await submit();
  if (res.status === 429) {
    // back off; per-(client × biller) limit is 100/hr
    await new Promise((r) => setTimeout(r, 60_000));
    res = await submit();  // safe: idempotencyKey is the same
  }
  if (!res.ok) {
    const error = await res.json();
    throw new Error(`feedback failed: ${error.error_code} — ${error.error_message}`);
  }
  return await res.json();  // { agent_type, run_id, ..., already_existed }
}

3. Handle errors

The endpoint has 6 documented error codes. The full catalog is at Feedback errors. Short version for the common paths:

  • 429 FEEDBACK_RATE_LIMITED — back off. The limit is per (client_id, biller_id); if many users are submitting on the same biller, queue.
  • 409 FEEDBACK_ALREADY_SUBMITTED — feedback already exists for this (client_id, run_id). Each run accepts exactly one submission. Treat as a successful outcome if you didn’t mean to overwrite.
  • 404 FEEDBACK_RUN_NOT_FOUND — run_id doesn’t exist. Don’t expose to the user; log + investigate.
  • 403 FEEDBACK_RUN_NOT_OWNED — the run_id exists but belongs to a different client. Audit your run_id storage; you should never see this in normal operation.
  • 410 FEEDBACK_RUN_EXPIRED — the run is older than 30 days. Feedback windows are bounded so the brain doesn’t train on stale signal.
  • 422 FEEDBACK_INVALID_CATEGORY — signal is missing or not one of the four enum values. Fix client-side validation; this shouldn’t reach users.

What happens to your feedback

Submitted feedback enters the per-biller signal pipeline:

  • succeeded — counts toward auto-promote thresholds for the prompt version that ran. Affirmative feedback is what lets winning versions graduate.
  • login_failed / bill_missing / data_incorrect — counts toward auto-rollback thresholds. A cluster (≥5 in 24h on the same biller) crosses the threshold and reverts the agent to its prior prompt version without human intervention.

This is why high-quality feedback matters. A noisy stream of unhelpful reports (mis-categorized signals, empty notes) dilutes the signal. Gate on user confirmation, populate notes with the specifics, and the per-biller pipeline compounds.

Best practices

  • Submit on success too. Most clients only file negative feedback. That biases the signal. succeeded submissions are what let us auto-promote new prompt versions safely.
  • Don’t auto-submit on every model output you don’t recognize. Gate on user confirmation. False-positive feedback poisons the signal.
  • Don’t reuse Idempotency-Key across different submissions. The key is per-submission, not per-user-session.
  • Use notes for specifics on data_incorrect. “Amount is wrong” is noise; “$0 returned but statement shows $84.32” is actionable.
  • Check already_existed in the response. Lets you distinguish “new feedback recorded” from “idempotent replay” without comparing timestamps.